* {
margin: 0;
padding: 0;
box-sizing: border-box;
}body {
font-family: ‘Poppins’, -apple-system, BlinkMacSystemFont, ‘Segoe UI’, Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f8fafc;
}.competition-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}.loader {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1000;
}.loader.active {
display: block;
}/* Styles communs avec nouveau thème */
.competition-header {
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
box-shadow: 0 4px 16px rgba(31, 41, 55, 0.3);
}.competition-header h1 {
font-size: 1.5rem;
margin: 0;
font-weight: 600;
}.view-toggle {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}.toggle-btn {
padding: 10px 20px;
background: white;
border: 2px solid #e5e7eb;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
color: #374151;
}.toggle-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}.toggle-btn.active {
background: #475569;
color: white;
border-color: #475569;
box-shadow: 0 2px 8px rgba(71, 85, 105, 0.3);
}.navigation-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
overflow-x: auto;
padding: 10px 0;
}.tab {
padding: 10px 20px;
background: white;
border: 2px solid #e5e7eb;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
color: #374151;
}.tab:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}.tab.active {
background: #475569;
color: white;
border-color: #475569;
box-shadow: 0 2px 8px rgba(71, 85, 105, 0.2);
}/* Cup/Tournament Styles */
.cup-view .phase-container {
margin-bottom: 40px;
}
.championship-view .phase-container,
.tournament-view .phase-container {
margin-bottom: 40px;
}.phase-header {
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
color: white;
padding: 15px;
border-radius: 5px 5px 0 0;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}.phase-type-badge {
background: rgba(255,255,255,0.2);
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
text-transform: uppercase;
}/* Header tab buttons */
.header-tab-btn {
padding: 8px 16px !important;
font-size: 13px !important;
font-weight: 500;
transition: all 0.3s ease !important;
}
.header-tab-btn:hover {
background: rgba(255,255,255,0.3) !important;
color: white !important;
transform: translateY(-1px);
}.header-tab-btn.active:hover {
background: rgba(255,255,255,0.25) !important;
}/* Match Cards – Simplified */
.match-card {
background: white;
border: 2px solid #e5e7eb;
border-radius: 10px;
margin-bottom: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
position: relative;
}.match-card:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
border-color: #475569;
}.match-card.finished {
border-color: #1e40af;
}.match-card.finished::before {
content: ‘✓’;
position: absolute;
top: 8px;
right: 8px;
color: #1e40af;
font-weight: bold;
font-size: 14px;
}.match-card.live {
border-color: #d62243;
animation: pulse-border 2s infinite;
}.match-card.live::before {
content: ‘● LIVE’;
position: absolute;
top: 8px;
right: 8px;
color: #d62243;
font-weight: bold;
font-size: 11px;
animation: pulse 2s infinite;
}@keyframes pulse-border {
0% { border-color: #d62243; }
50% { border-color: #e85a75; }
100% { border-color: #d62243; }
}/* Desktop layout */
@media (min-width: 769px) {
.match-team {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #f3f4f6;
transition: all 0.2s;
position: relative;
}.match-team:last-child {
border-bottom: none;
}.match-team.winner {
background: linear-gradient(to right, #dbeafe 0%, #eff6ff 100%);
font-weight: 600;
}.match-team.winner::before {
content: »;
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: #1e40af;
}.match-team.loser {
opacity: 0.7;
}.match-team-logo {
width: 32px;
height: 32px;
object-fit: contain;
margin-right: 12px;
border-radius: 4px;
background: #f9fafb;
padding: 2px;
}.match-team-name {
flex: 1;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #374151;
font-family: ‘Poppins’, sans-serif;
}.match-team-score {
font-size: 18px;
font-weight: bold;
color: #475569;
min-width: 30px;
text-align: center;
background: #f1f5f9;
padding: 4px 10px;
border-radius: 6px;
}
}/* Mobile layout – simplified */
@media (max-width: 768px) {
.match-card {
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 15px;
position: relative;
}.match-team {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
padding: 0;
border: none;
background: none !important;
}.match-team::before {
display: none;
}.match-team-logo {
width: 40px;
height: 40px;
margin-bottom: 6px;
border-radius: 4px;
}.match-team-name {
font-size: 11px;
font-weight: 600;
max-width: 80px;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #374151;
font-family: ‘Poppins’, sans-serif;
}/* Center content (score or date) */
.match-center-score {
font-size: 20px;
font-weight: bold;
color: #475569;
background: #f1f5f9;
padding: 10px 15px;
border-radius: 8px;
text-align: center;
min-width: 70px;
}.match-center-score.live {
background: #d62243;
color: white;
animation: pulse 2s infinite;
}.match-center-date {
text-align: center;
padding: 5px 10px;
}.match-date-day {
font-size: 12px;
font-weight: 600;
color: #475569;
margin-bottom: 2px;
}.match-date-time {
font-size: 11px;
color: #6b7280;
font-weight: 500;
}.match-info {
display: none;
}.match-status {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
border-radius: 4px 4px 0 0;
}.match-card.finished .match-status {
background: #1e40af;
}.match-card.live .match-status {
background: #d62243;
animation: pulse 2s infinite;
}
}.bracket-match-info {
background: #f9fafb;
padding: 6px 12px;
text-align: center;
font-size: 11px;
color: #6b7280;
border-top: 1px solid #e5e7eb;
}.bracket-match-date {
font-weight: 500;
color: #374151;
}.bracket-match-location {
margin-top: 1px;
font-size: 10px;
}/* Match Status Indicator */
.match-status {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: #e5e7eb;
}.bracket-match.finished .match-status {
background: #1e40af;
}.bracket-match.live .match-status {
background: #d62243;
animation: pulse 2s infinite;
}/* Bracket Navigation for Mobile */
.bracket-navigation {
display: none;
position: sticky;
top: 0;
background: white;
padding: 10px;
border-bottom: 2px solid #e5e7eb;
margin-bottom: 15px;
z-index: 100;
}@media (max-width: 768px) {
.bracket-navigation {
display: flex;
gap: 10px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}.bracket-nav-btn {
padding: 8px 16px;
background: #f1f5f9;
border: 2px solid #475569;
border-radius: 20px;
color: #475569;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s ease;
}.bracket-nav-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(71, 85, 105, 0.2);
}.bracket-nav-btn.active {
background: #475569;
color: white;
}
}/* List View for Groups */
.matches-list {
background: white;
border-radius: 0 0 5px 5px;
padding: 20px;
}.match-row {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 15px;
padding: 15px;
background: #f9fafb;
border-radius: 8px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.3s ease;
align-items: center;
border: 2px solid transparent;
}.match-row:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-color: #475569;
}.team-info {
display: flex;
align-items: center;
gap: 10px;
}.team-info.away {
flex-direction: row-reverse;
text-align: right;
}.team-logo {
width: 40px;
height: 40px;
object-fit: contain;
}.match-center {
text-align: center;
min-width: 120px;
}.score-box {
background: #374151;
color: white;
padding: 8px 16px;
border-radius: 5px;
font-weight: bold;
display: inline-block;
}.score-box.live {
background: #d62243;
animation: pulse 2s infinite;
}@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.8; }
100% { opacity: 1; }
}.match-datetime {
font-size: 14px;
color: #6b7280;
line-height: 1.4;
}.match-time {
display: block;
font-weight: bold;
color: #374151;
margin-top: 4px;
}/* Enhanced Responsive Design */
@media (max-width: 1200px) {
.bracket-container {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
}
}@media (max-width: 768px) {
.competition-container {
padding: 10px;
}.tournament-bracket {
padding: 0;
}.match-row {
grid-template-columns: 1fr;
gap: 8px;
padding: 12px;
}.team-info {
justify-content: center;
}.team-info.away {
flex-direction: row;
text-align: left;
}.view-toggle {
width: 100%;
justify-content: stretch;
}.toggle-btn {
flex: 1;
padding: 12px 10px;
font-size: 14px;
}.competition-header {
padding: 15px;
}.competition-header h1 {
font-size: 20px;
}.phase-header {
padding: 12px;
font-size: 14px;
}.match-center {
min-width: 80px;
}.score-box {
padding: 6px 12px;
font-size: 14px;
}
}/* Ultra small devices */
@media (max-width: 480px) {
.bracket-match {
margin-bottom: 8px;
}.team-logo {
width: 30px;
height: 30px;
}.rounds-dropdown-btn {
padding: 10px 15px;
font-size: 14px;
}
}/* Phase type specific styling */
.phase-container.knockout .phase-header {
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
}.phase-container.group .phase-header {
background: linear-gradient(135deg, #004382 0%, #003770 100%);
}/* Dropdown selector styles */
.rounds-dropdown-btn:hover {
background: #1f2937 !important;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(31, 41, 55, 0.3);
}.rounds-dropdown-option:hover {
background-color: #f9fafb !important;
}.rounds-dropdown-option:first-child {
border-radius: 6px 6px 0 0;
}.rounds-dropdown-option:last-child {
border-radius: 0 0 6px 6px;
border-bottom: none !important;
}/* Responsive dropdown */
@media (max-width: 768px) {
.rounds-dropdown-btn {
min-width: 100% !important;
justify-content: center !important;
}
.rounds-dropdown-menu {
left: 0 !important;
right: 0 !important;
}
}/* Bracket Card View for Complex Brackets on Mobile */
.bracket-cards-view {
display: none;
}@media (max-width: 768px) {
.bracket-cards-view {
display: block;
padding: 15px;
}.bracket-phase-card {
background: white;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
}.bracket-phase-card-header {
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
color: white;
padding: 15px;
font-weight: 600;
font-size: 16px;
text-align: center;
}.bracket-phase-card-content {
padding: 15px;
}.bracket-phase-card.final .bracket-phase-card-header {
background: linear-gradient(135deg, #d97706 0%, #f59e0b 100%);
}.bracket-phase-card.semifinal .bracket-phase-card-header {
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
}
}class CompetitionManager {
constructor() {
this.competitionId = this.getCompetitionIdFromUrl();
this.apiBase = ‘https://rugby-app-4ebacff10fda.herokuapp.com’;
this.currentData = null;
this.updateInterval = null;
this.cable = null;
this.competitionChannel = null;
}getCompetitionIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(‘competition’) || ‘pro-d2’;
}async init() {
await this.loadCompetitionData();
this.setupResponsiveHandler();
this.setupWebSocket();
this.startAutoUpdate();
}setupResponsiveHandler() {
let resizeTimeout;
window.addEventListener(‘resize’, () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
this.render();
}, 250);
});
}setupWebSocket() {
try {
// Configuration basique du WebSocket
const wsUrl = this.apiBase.replace(‘http://’, ‘ws://’).replace(‘https://’, ‘wss://’) + ‘/cable’;
console.log(‘Tentative de connexion WebSocket à:’, wsUrl);
this.cable = new WebSocket(wsUrl);
this.cable.onopen = () => {
console.log(‘WebSocket connecté avec succès’);
this.subscribeToCompetition();
};this.cable.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleWebSocketMessage(data);
} catch (parseError) {
console.warn(‘Erreur parsing message WebSocket:’, parseError);
}
};this.cable.onclose = (event) => {
console.log(‘WebSocket fermé:’, event.code, event.reason);
console.log(‘Tentative de reconnexion dans 5 secondes…’);
setTimeout(() => this.setupWebSocket(), 5000);
};this.cable.onerror = (error) => {
console.error(‘Erreur WebSocket:’, error);
};
} catch (error) {
console.warn(‘WebSocket non disponible, utilisation du polling seulement:’, error);
// Fallback: augmenter la fréquence du polling si WebSocket échoue
this.startAutoUpdate(10000); // Polling toutes les 10 secondes
}
}subscribeToCompetition() {
if (this.cable && this.cable.readyState === WebSocket.OPEN) {
const subscription = {
command: ‘subscribe’,
identifier: JSON.stringify({
channel: ‘CompetitionChannel’,
competition_id: this.competitionId
})
};
this.cable.send(JSON.stringify(subscription));
}
}handleWebSocketMessage(data) {
if (data.type === ‘ping’) return;
console.log(‘Message WebSocket reçu:’, data);
if (data.message) {
const message = data.message;
switch (message.type) {
case ‘score_update’:
case ‘live_score_update’:
this.updateMatchInUI(message.rencontre);
this.showNotification(`Score mis à jour: ${message.rencontre.local_team.name} vs ${message.rencontre.visitor_team.name}`);
break;
case ‘state_change’:
this.updateMatchStateInUI(message.rencontre_id, message.state);
this.showNotification(`État du match changé: ${message.state}`);
break;
}
}
}updateMatchInUI(rencontreData) {
console.log(‘Updating match UI for:’, rencontreData);
// Trouver et mettre à jour les éléments de match dans l’interface
const matchElements = document.querySelectorAll(`[data-match-id= »${rencontreData.id} »]`);
console.log(‘Found match elements:’, matchElements.length);
matchElements.forEach(element => {
const homeScore = rencontreData.score_locale || 0;
const awayScore = rencontreData.score_visiteuse || 0;
const isLive = rencontreData.etat === ‘endirect’ || rencontreData.etat === ‘mi-temps’;
const isFinished = rencontreData.etat === ‘termine’;
console.log(‘Updating element with scores:’, homeScore, awayScore, ‘state:’, rencontreData.etat);
// Mobile layout
if (window.innerWidth <= 768) {
let centerElement = element.querySelector('.match-center-score, .match-center-date');
if (isLive || isFinished) {
// Si le match est live ou terminé, on doit afficher le score
if (!element.querySelector('.match-center-score')) {
// Remplacer le container de date par le container de score
const dateElement = element.querySelector('.match-center-date');
if (dateElement) {
const scoreElement = document.createElement('div');
scoreElement.className = `match-center-score ${isLive ? 'live' : ''}`;
scoreElement.textContent = `${homeScore} – ${awayScore}`;
dateElement.parentNode.replaceChild(scoreElement, dateElement);
}
} else {
// Mettre à jour le score existant
const centerScore = element.querySelector('.match-center-score');
if (centerScore) {
centerScore.textContent = `${homeScore} – ${awayScore}`;
if (isLive) {
centerScore.classList.add('live');
} else {
centerScore.classList.remove('live');
}
}
}
}
} else {
// Desktop layout
const matchCenter = element.querySelector('.match-center');
if (matchCenter) {
if (isLive || isFinished) {
// Si le match est live ou terminé, remplacer par score-box
if (!element.querySelector('.score-box')) {
matchCenter.innerHTML = `
${homeScore} – ${awayScore}
`;
} else {
// Mettre à jour le score existant
const scoreBox = element.querySelector(‘.score-box’);
if (scoreBox) {
scoreBox.textContent = `${homeScore} – ${awayScore}`;
if (isLive) {
scoreBox.classList.add(‘live’);
} else {
scoreBox.classList.remove(‘live’);
}
}
}
}
}
}
// Mettre à jour l’état visuel de la card
element.classList.remove(‘finished’, ‘live’);
if (isFinished) {
element.classList.add(‘finished’);
} else if (isLive) {
element.classList.add(‘live’);
}
console.log(‘Updated match state classes’);
});
}updateMatchStateInUI(rencontreId, newState) {
const matchElements = document.querySelectorAll(`[data-match-id= »${rencontreId} »]`);
matchElements.forEach(element => {
// Réinitialiser les classes d’état
element.className = element.className.replace(/\b(finished|live|scheduled)\b/g, »);
// Ajouter la nouvelle classe selon l’état
switch (newState) {
case ‘termine’:
element.classList.add(‘finished’);
break;
case ‘endirect’:
case ‘mi-temps’:
element.classList.add(‘live’);
break;
case ‘programme’:
element.classList.add(‘scheduled’);
break;
}
});
}showNotification(message) {
// Créer une notification simple
const notification = document.createElement(‘div’);
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #374151;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
z-index: 10000;
font-size: 14px;
max-width: 300px;
transition: all 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
// Animation d’apparition
setTimeout(() => {
notification.style.transform = ‘translateX(0)’;
notification.style.opacity = ‘1’;
}, 100);
// Supprimer après 5 secondes
setTimeout(() => {
notification.style.transform = ‘translateX(100%)’;
notification.style.opacity = ‘0’;
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 5000);
}showLoader() {
document.getElementById(‘loader’).classList.add(‘active’);
}hideLoader() {
document.getElementById(‘loader’).classList.remove(‘active’);
}async loadCompetitionData() {
this.showLoader();
try {
const response = await fetch(`${this.apiBase}/competitions/${this.competitionId}/adaptive`);
this.currentData = await response.json();
this.render();
} catch (error) {
console.error(‘Erreur lors du chargement:’, error);
this.showError(‘Erreur lors du chargement des données’);
} finally {
this.hideLoader();
}
}render() {
if (!this.currentData) return;// Update header (only title, no date)
document.getElementById(‘competitionTitle’).textContent = this.currentData.competition.display_name;
document.getElementById(‘competitionInfo’).textContent = »;// Store competition date for inline display
this.competitionDate = this.currentData.competition.years || »;// Render based on competition type
const mainContent = document.getElementById(‘mainContent’);
mainContent.innerHTML = »;switch (this.currentData.type) {
case ‘championship’:
this.renderChampionship(mainContent);
break;
case ‘cup’:
this.renderCup(mainContent);
break;
case ‘tournament’:
this.renderTournament(mainContent);
break;
default:
this.renderDefault(mainContent);
}// Update inline competition dates after rendering
this.updateInlineCompetitionDates();
}updateInlineCompetitionDates() {
// Update all inline competition date elements
const inlineDateElements = document.querySelectorAll(‘.competition-date-inline’);
inlineDateElements.forEach(element => {
element.textContent = this.competitionDate;
});
}renderCup(container) {
const view = document.createElement(‘div’);
view.className = ‘cup-view’;// Render all phases as list
this.renderListView(view, this.currentData.phases);container.appendChild(view);
}groupPhasesByType(phases) {
const grouped = {
group: [],
knockout: []
};phases.forEach(phase => {
if (phase.type === ‘knockout’) {
grouped.knockout.push(phase);
} else {
grouped.group.push(phase);
}
});return grouped;
}renderListView(container, phases) {
phases.forEach(phase => {
const phaseContainer = this.createPhaseContainer(phase, false);
container.appendChild(phaseContainer);
});
}createPhaseContainer(phase) {
const phaseContainer = document.createElement(‘div’);
phaseContainer.className = `phase-container ${phase.type}`;const phaseHeader = document.createElement(‘div’);
phaseHeader.className = ‘phase-header’;
phaseHeader.innerHTML = `
${phase.name} Choisir une journée…
${phase.type === ‘group’ ? ‘‘ : »}
${phase.type === ‘group’ ? ‘‘ : »}
`;
phaseContainer.appendChild(phaseHeader);// Add event listeners to header buttons
const headerBtns = phaseHeader.querySelectorAll(‘.header-tab-btn’);
headerBtns.forEach(btn => {
btn.addEventListener(‘click’, (e) => {
e.stopPropagation();
const tabType = btn.dataset.tab;
// Update button styles for all tabs
headerBtns.forEach(b => {
b.classList.remove(‘active’);
b.style.background = ‘transparent’;
b.style.color = ‘rgba(255,255,255,0.7)’;
});
btn.classList.add(‘active’);
btn.style.background = ‘rgba(255,255,255,0.2)’;
btn.style.color = ‘white’;
this.showPhaseTab(phaseContainer, phase, tabType);
});
});// Initial content
const contentContainer = document.createElement(‘div’);
contentContainer.className = ‘phase-content’;
phaseContainer.appendChild(contentContainer);
// Show rounds by default (matches)
this.showPhaseTab(phaseContainer, phase, ’rounds’);return phaseContainer;
}showPhaseTab(container, phase, tab) {
// Update content
const contentArea = container.querySelector(‘.phase-content’);
contentArea.innerHTML = »;
if (tab === ’rounds’) {
const matchesContainer = this.createMatchesListGrouped(phase.matches);
contentArea.appendChild(matchesContainer);
// Initialize header selector with journée data
const headerSelector = container.querySelector(‘.journee-selector-header’);
if (headerSelector && matchesContainer.journeeData) {
const { sortedJournees, defaultJournee } = matchesContainer.journeeData;
// Clear existing options
headerSelector.innerHTML = »;
// Add journée options
console.log(‘Initializing selector with default journée:’, defaultJournee);
sortedJournees.forEach(journee => {
const option = document.createElement(‘option’);
option.value = journee;
option.textContent = journee;
if (journee === defaultJournee) {
option.selected = true;
console.log(‘Selected option:’, journee);
}
headerSelector.appendChild(option);
});
// Set the selected value explicitly
headerSelector.value = defaultJournee;
console.log(‘Set selector value to:’, headerSelector.value);
// Remove existing event listeners and add new one
const newHeaderSelector = headerSelector.cloneNode(true);
newHeaderSelector.value = defaultJournee; // Ensure value is maintained after cloning
headerSelector.parentNode.replaceChild(newHeaderSelector, headerSelector);
newHeaderSelector.addEventListener(‘change’, (e) => {
if (matchesContainer.showJourneeMatches) {
matchesContainer.showJourneeMatches(e.target.value);
}
});
// Show the selector
newHeaderSelector.style.display = ‘block’;
console.log(‘Final selector value:’, newHeaderSelector.value);
}
} else if (tab === ‘standings’) {
// Keep header selector visible
const headerSelector = container.querySelector(‘.journee-selector-header’);
if (headerSelector) {
headerSelector.style.display = ‘block’;
}
this.renderPhaseStandings(contentArea, phase);
}
}renderPhaseStandings(container, phase) {
const standingsContainer = document.createElement(‘div’);
standingsContainer.className = ‘standings-table’;
standingsContainer.style.cssText = ‘background: white; border-radius: 5px; overflow: hidden; margin-top: 20px;’;
if (phase.standings && phase.standings.length > 0) {
const table = document.createElement(‘table’);
table.style.cssText = ‘width: 100%; border-collapse: collapse;’;
// Header
const thead = document.createElement(‘thead’);
const headerRow = document.createElement(‘tr’);
headerRow.style.cssText = ‘background: #f3f4f6; border-bottom: 2px solid #e5e7eb;’;
headerRow.innerHTML = `
Équipe J V D +/- Pts `;
thead.appendChild(headerRow);
table.appendChild(thead);
// Body
const tbody = document.createElement(‘tbody’);
phase.standings.forEach((team, index) => {
const row = document.createElement(‘tr’);
row.style.cssText = `border-bottom: 1px solid #e5e7eb; ${index % 2 === 0 ? ‘background: #f9fafb;’ : »} transition: background-color 0.2s ease;`;
row.innerHTML = `
${team.rank || index + 1}. ${team.team_name} ${team.matchs_joues} ${team.matchs_gagnes} ${team.matchs_perdus} = 0 ? ‘#059669’ : ‘#dc2626’}; font-weight: 500; »>${team.diff_buts >= 0 ? ‘+’ : »}${team.diff_buts} ${team.points} `;
tbody.appendChild(row);
});
table.appendChild(tbody);
standingsContainer.appendChild(table);
} else {
const noStandings = document.createElement(‘p’);
noStandings.textContent = ‘Aucun classement disponible’;
noStandings.style.cssText = ‘color: #6b7280; text-align: center; padding: 40px;’;
standingsContainer.appendChild(noStandings);
}
container.appendChild(standingsContainer);
}renderChampionship(container) {
const view = document.createElement(‘div’);
view.className = ‘championship-view’;// Render each group
this.currentData.groups.forEach(group => {
const groupContainer = this.createGroupContainer(group);
view.appendChild(groupContainer);
});container.appendChild(view);
}createGroupContainer(group) {
const groupContainer = document.createElement(‘div’);
groupContainer.className = ‘phase-container group’;const groupHeader = document.createElement(‘div’);
groupHeader.className = ‘phase-header’;
groupHeader.innerHTML = `
${group.name === ‘1’ ? ‘Top 14 ’ : `Groupe ${group.name}`}
`;
groupContainer.appendChild(groupHeader);// Add event listeners to header buttons
const headerBtns = groupHeader.querySelectorAll(‘.header-tab-btn’);
headerBtns.forEach(btn => {
btn.addEventListener(‘click’, (e) => {
e.stopPropagation();
const tabType = btn.dataset.tab;
// Update button styles with smooth transition
headerBtns.forEach(b => {
b.classList.remove(‘active’);
b.style.transition = ‘all 0.3s ease’;
b.style.background = ‘transparent’;
b.style.color = ‘rgba(255,255,255,0.7)’;
});
btn.classList.add(‘active’);
btn.style.transition = ‘all 0.3s ease’;
btn.style.background = ‘rgba(255,255,255,0.2)’;
btn.style.color = ‘white’;
this.switchGroupTab(groupContainer, tabType, group);
// Update inline dates after tab switch
setTimeout(() => this.updateInlineCompetitionDates(), 50);
});
});// Content area
const contentArea = document.createElement(‘div’);
contentArea.className = ‘group-content’;
groupContainer.appendChild(contentArea);// Initially show rounds
this.renderGroupRounds(contentArea, group);return groupContainer;
}switchGroupTab(container, tab, group) {
// Update tab styles
const tabs = container.querySelectorAll(‘.tab’);
tabs.forEach(t => t.classList.remove(‘active’));
const activeTab = Array.from(tabs).find(t =>
(tab === ’rounds’ && t.textContent === ‘Journées’) ||
(tab === ‘standings’ && t.textContent === ‘Classement’)
);
if (activeTab) activeTab.classList.add(‘active’);// Update content
const contentArea = container.querySelector(‘.group-content’);
contentArea.innerHTML = »;
if (tab === ’rounds’) {
this.renderGroupRounds(contentArea, group);
} else {
this.renderGroupStandings(contentArea, group);
}
}renderGroupRounds(container, group) {
const roundsContainer = document.createElement(‘div’);
roundsContainer.className = ‘matches-list’;
if (group.rounds && group.rounds.length > 0) {
group.rounds.forEach(round => {
const roundHeader = document.createElement(‘h3’);
roundHeader.textContent = round.name;
roundHeader.style.cssText = ‘margin: 20px 0 10px 0; color: #475569;’;
roundsContainer.appendChild(roundHeader);
if (round.matches && round.matches.length > 0) {
round.matches.forEach(match => {
const matchCard = this.createMatchCard(match);
roundsContainer.appendChild(matchCard);
});
} else {
const noMatches = document.createElement(‘p’);
noMatches.textContent = ‘Aucun match programmé’;
noMatches.style.cssText = ‘color: #6b7280; font-style: italic; margin-bottom: 20px; padding-left: 20px;’;
roundsContainer.appendChild(noMatches);
}
});
} else {
const noRounds = document.createElement(‘p’);
noRounds.textContent = ‘Aucune journée disponible’;
noRounds.style.cssText = ‘color: #6b7280; text-align: center; padding: 40px;’;
roundsContainer.appendChild(noRounds);
}
container.appendChild(roundsContainer);
}renderGroupStandings(container, group) {
const standingsContainer = document.createElement(‘div’);
standingsContainer.className = ‘standings-table’;
standingsContainer.style.cssText = ‘background: white; border-radius: 5px; overflow: hidden; margin-top: 20px;’;
if (group.standings && group.standings.length > 0) {
const table = document.createElement(‘table’);
table.style.cssText = ‘width: 100%; border-collapse: collapse;’;
// Header
const header = document.createElement(‘thead’);
header.innerHTML = `
Pos Équipe J G N P +/- Pts `;
table.appendChild(header);
// Body
const tbody = document.createElement(‘tbody’);
group.standings.forEach((team, index) => {
const row = document.createElement(‘tr’);
row.style.cssText = `border-bottom: 1px solid #e5e7eb; ${index % 2 === 0 ? ‘background: #f9fafb;’ : »} transition: background-color 0.2s ease;`;
row.innerHTML = `
${index + 1} ${team.team_name} ${team.matchs_joues || 0} ${team.matchs_gagnes || 0} ${team.matchs_nuls || 0} ${team.matchs_perdus || 0} ${team.diff_buts || 0} ${team.points || 0} `;
tbody.appendChild(row);
});
table.appendChild(tbody);
standingsContainer.appendChild(table);
} else {
const noStandings = document.createElement(‘p’);
noStandings.textContent = ‘Aucun classement disponible’;
noStandings.style.cssText = ‘color: #6b7280; text-align: center; padding: 40px;’;
standingsContainer.appendChild(noStandings);
}
container.appendChild(standingsContainer);
}renderTournament(container) {
const view = document.createElement(‘div’);
view.className = ‘tournament-view’;// Group phases by type for better organization
const groupedPhases = this.groupPhasesByType(this.currentData.phases);// Render championship phase with rounds navigation
if (groupedPhases.group.length > 0) {
const championshipPhase = groupedPhases.group[0];
this.renderChampionshipPhase(view, championshipPhase);
}// Render knockout phases as list
if (groupedPhases.knockout.length > 0) {
this.renderListView(view, groupedPhases.knockout);
}container.appendChild(view);
}renderChampionshipPhase(container, phase) {
const phaseContainer = document.createElement(‘div’);
phaseContainer.className = ‘phase-container group’;const phaseHeader = document.createElement(‘div’);
phaseHeader.className = ‘phase-header’;
phaseHeader.innerHTML = `
${phase.name}
`;
phaseContainer.appendChild(phaseHeader);// Add event listeners to header buttons
const headerBtns = phaseHeader.querySelectorAll(‘.header-tab-btn’);
headerBtns.forEach(btn => {
btn.addEventListener(‘click’, (e) => {
e.stopPropagation();
const tabType = btn.dataset.tab;
// Update button styles with smooth transition
headerBtns.forEach(b => {
b.classList.remove(‘active’);
b.style.transition = ‘all 0.3s ease’;
b.style.background = ‘transparent’;
b.style.color = ‘rgba(255,255,255,0.7)’;
});
btn.classList.add(‘active’);
btn.style.transition = ‘all 0.3s ease’;
btn.style.background = ‘rgba(255,255,255,0.2)’;
btn.style.color = ‘white’;
this.switchChampionshipTab(phaseContainer, tabType, phase);
// Update inline dates after tab switch
setTimeout(() => this.updateInlineCompetitionDates(), 50);
});
});// Setup rounds dropdown in header
this.setupHeaderRoundsDropdown(phaseHeader, phase, phaseContainer);// Content area
const contentArea = document.createElement(‘div’);
contentArea.className = ‘championship-content’;
phaseContainer.appendChild(contentArea);// Initially show rounds with navigation
this.renderChampionshipRounds(contentArea, phase);container.appendChild(phaseContainer);
}switchChampionshipTab(container, tab, phase) {
// Update tab styles
const tabs = container.querySelectorAll(‘.tab’);
tabs.forEach(t => t.classList.remove(‘active’));
const activeTab = Array.from(tabs).find(t =>
(tab === ’rounds’ && t.textContent === ‘Journées’) ||
(tab === ‘standings’ && t.textContent === ‘Classement’)
);
if (activeTab) activeTab.classList.add(‘active’);// Update content
const contentArea = container.querySelector(‘.championship-content’);
contentArea.innerHTML = »;
if (tab === ’rounds’) {
this.renderChampionshipRounds(contentArea, phase);
} else {
this.renderChampionshipStandings(contentArea, phase);
}
}renderChampionshipRounds(container, phase) {
// Matches container only – selector is now in header
const matchesContainer = document.createElement(‘div’);
matchesContainer.className = ’round-matches’;
container.appendChild(matchesContainer);// Show first round by default
if (phase.rounds && phase.rounds.length > 0) {
this.loadRoundMatches(matchesContainer, phase.rounds[0]);
}
}setupHeaderRoundsDropdown(phaseHeader, phase, phaseContainer) {
const dropdownBtn = phaseHeader.querySelector(‘.rounds-dropdown-btn-header’);
const dropdownMenu = phaseHeader.querySelector(‘.rounds-dropdown-menu-header’);
if (!dropdownBtn || !dropdownMenu) return;// Populate dropdown menu with rounds
if (phase.rounds && phase.rounds.length > 0) {
phase.rounds.forEach((round, index) => {
const option = document.createElement(‘div’);
option.className = ’rounds-dropdown-option-header’;
option.style.cssText = `
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #e5e7eb;
transition: all 0.3s ease;
font-size: 12px;
color: #374151;
`;
option.textContent = round.name;// Hover effects
option.onmouseenter = () => {
option.style.backgroundColor = ‘#f9fafb’;
};
option.onmouseleave = () => {
option.style.backgroundColor = ‘transparent’;
};// Click handler
option.onclick = () => {
// Update button text
dropdownBtn.querySelector(‘span:first-child’).textContent = round.name;
// Hide dropdown
dropdownMenu.style.display = ‘none’;
dropdownBtn.querySelector(‘span:last-child’).textContent = ‘▼’;
// Load round matches
const matchesContainer = phaseContainer.querySelector(‘.round-matches’);
if (matchesContainer) {
this.loadRoundMatches(matchesContainer, round);
}
};if (index === phase.rounds.length – 1) {
option.style.borderBottom = ‘none’;
}dropdownMenu.appendChild(option);
});
}// Toggle dropdown on button click
dropdownBtn.onclick = (e) => {
e.stopPropagation();
const isVisible = dropdownMenu.style.display === ‘block’;
dropdownMenu.style.display = isVisible ? ‘none’ : ‘block’;
dropdownBtn.querySelector(‘span:last-child’).textContent = isVisible ? ‘▼’ : ‘▲’;
};// Close dropdown when clicking outside
document.addEventListener(‘click’, (e) => {
if (!phaseHeader.querySelector(‘.rounds-selector-header’).contains(e.target)) {
dropdownMenu.style.display = ‘none’;
dropdownBtn.querySelector(‘span:last-child’).textContent = ‘▼’;
}
});
}async selectRoundById(container, round, selectedOption) {
// Display matches for selected round
const matchesContainer = container.querySelector(‘.round-matches’);
this.loadRoundMatches(matchesContainer, round);
}async loadRoundMatches(container, round) {
// Show loading
container.innerHTML = ‘
Chargement…
‘;try {
const response = await fetch(`${this.apiBase}/competitions/${this.competitionId}/rounds/${round.id}`);
const data = await response.json();
this.displayRoundMatches(container, data.matches);
} catch (error) {
console.error(‘Error loading round matches:’, error);
container.innerHTML = ‘
Erreur lors du chargement des matches
‘;
}
}groupMatchesByRound(matches) {
const grouped = {};
matches.forEach(match => {
const roundName = this.extractRoundName(match);
if (!grouped[roundName]) {
grouped[roundName] = [];
}
grouped[roundName].push(match);
});
return grouped;
}extractRoundName(match) {
// Try to extract round name from match data
if (match.phase_name) {
return match.phase_name;
}
// Fallback to date-based grouping
const date = new Date(match.date);
return `Journée du ${date.toLocaleDateString(‘fr-FR’, { day: ‘numeric’, month: ‘short’ })}`;
}selectRound(container, roundName, matchesByRound, selectedBtn) {
// Update button styles
const buttons = container.querySelectorAll(‘.round-btn’);
buttons.forEach(btn => {
btn.style.background = ‘white’;
btn.style.color = ‘#374151’;
btn.style.borderColor = ‘#e5e7eb’;
btn.classList.remove(‘active’);
});
selectedBtn.style.background = ‘#475569’;
selectedBtn.style.color = ‘white’;
selectedBtn.style.borderColor = ‘#475569’;
selectedBtn.classList.add(‘active’);// Display matches for selected round
const matchesContainer = container.querySelector(‘.round-matches’);
this.displayRoundMatches(matchesContainer, matchesByRound[roundName]);
}displayRoundMatches(container, matches) {
container.innerHTML = »;
const matchesList = document.createElement(‘div’);
matchesList.className = ‘matches-list’;
matches.forEach(match => {
const matchCard = this.createMatchCard(match);
matchesList.appendChild(matchCard);
});
container.appendChild(matchesList);
}renderChampionshipStandings(container, phase) {
const standingsContainer = document.createElement(‘div’);
standingsContainer.className = ‘standings-table’;
standingsContainer.style.cssText = ‘background: white; border-radius: 5px; overflow: hidden; margin-top: 20px;’;
if (phase.standings && phase.standings.length > 0) {
const table = document.createElement(‘table’);
table.style.cssText = ‘width: 100%; border-collapse: collapse;’;
// Header
const header = document.createElement(‘thead’);
header.innerHTML = `
Pos Équipe J G N P +/- Pts `;
table.appendChild(header);
// Body
const tbody = document.createElement(‘tbody’);
phase.standings.forEach((team, index) => {
const row = document.createElement(‘tr’);
row.style.cssText = `border-bottom: 1px solid #e5e7eb; ${index % 2 === 0 ? ‘background: #f9fafb;’ : »} transition: background-color 0.2s ease;`;
row.innerHTML = `
${index + 1} ${team.team_name} ${team.matchs_joues || 0} ${team.matchs_gagnes || 0} ${team.matchs_nuls || 0} ${team.matchs_perdus || 0} ${team.diff_buts || 0} ${team.points || 0} `;
tbody.appendChild(row);
});
table.appendChild(tbody);
standingsContainer.appendChild(table);
} else {
const noStandings = document.createElement(‘p’);
noStandings.textContent = ‘Aucun classement disponible’;
noStandings.style.cssText = ‘color: #6b7280; text-align: center; padding: 40px;’;
standingsContainer.appendChild(noStandings);
}
container.appendChild(standingsContainer);
}renderDefault(container) {
// Fallback rendering
const matchesList = this.createMatchesList(this.currentData.matches || []);
container.appendChild(matchesList);
}createMatchesListGrouped(matches) {
const container = document.createElement(‘div’);
container.className = ‘matches-list-grouped’;// Group matches by journée (phase_name)
const matchesByJournee = {};
matches.forEach(match => {
const journee = match.phase_name || ‘Matches’;
if (!matchesByJournee[journee]) {
matchesByJournee[journee] = [];
}
matchesByJournee[journee].push(match);
});// Sort journées chronologically
const sortedJournees = Object.keys(matchesByJournee).sort((a, b) => {
// Extract number from « 1re journée », « 2e journée », etc.
const getJourneeNumber = (str) => {
const match = str.match(/(\d+)[re]+\s+journ/i);
return match ? parseInt(match[1]) : 999;
};
return getJourneeNumber(a) – getJourneeNumber(b);
});if (sortedJournees.length === 0) {
const noMatches = document.createElement(‘div’);
noMatches.style.cssText = ‘text-align: center; padding: 40px; color: #6b7280; background: white; border-radius: 8px;’;
noMatches.innerHTML = `
⚽
Aucun match disponible
À lire aussi | Les scores à la mi-temps de la Pro D2
`;
container.appendChild(noMatches);
return container;
}// Find the closest journée to current date
const getCurrentClosestJournee = () => {
const today = new Date();
today.setHours(0, 0, 0, 0); // Reset time to compare only dates
let closestJournee = sortedJournees[0];
let minDiff = Infinity;console.log(‘Looking for closest journée to today:’, today.toDateString());sortedJournees.forEach(journee => {
const journeeMatches = matchesByJournee[journee];
if (journeeMatches.length > 0) {
// Get the earliest match date in this journée
const earliestMatch = journeeMatches.reduce((earliest, match) => {
const matchDate = new Date(match.date);
const earliestDate = new Date(earliest.date);
return matchDate < earliestDate ? match : earliest;
});
const matchDate = new Date(earliestMatch.date);
matchDate.setHours(0, 0, 0, 0); // Reset time to compare only dates
const diff = Math.abs(matchDate – today);
console.log(`Journée "${journee}": earliest match ${matchDate.toDateString()}, diff: ${diff} days`);
if (diff {
contentArea.innerHTML = »;
if (!matchesByJournee[selectedJournee]) return;const journeeSection = document.createElement(‘div’);
journeeSection.className = ‘journee-section’;// Matches container (sans header vert)
const matchesContainer = document.createElement(‘div’);
matchesContainer.className = ‘journee-matches’;
matchesContainer.style.cssText = ‘background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);’;
matchesByJournee[selectedJournee].forEach((match, index) => {
const matchCard = this.createMatchCard(match);
matchCard.style.borderRadius = ‘0’;
matchCard.style.marginBottom = ‘0’;
if (index {
const matchCard = this.createMatchCard(match);
container.appendChild(matchCard);
});return container;
}createMatchCard(match) {
const isFinished = match.etat === ‘termine’;
const isLive = match.etat === ‘endirect’;
const card = document.createElement(‘div’);
card.className = `match-card ${isFinished ? ‘finished’ : »} ${isLive ? ‘live’ : »}`;
card.setAttribute(‘data-match-id’, match.id);
card.onclick = () => window.location.href = `https://vibrez-rugby.com/live-en-direct/?id=${match.id}`;// Add status indicator
const statusBar = document.createElement(‘div’);
statusBar.className = ‘match-status’;
card.appendChild(statusBar);// Determine winner if match is finished
let homeWinner = false, awayWinner = false;
if (isFinished && match.score_locale !== null && match.score_visiteuse !== null) {
const homeScore = parseInt(match.score_locale) || 0;
const awayScore = parseInt(match.score_visiteuse) || 0;
if (homeScore > awayScore) {
homeWinner = true;
} else if (awayScore > homeScore) {
awayWinner = true;
}
}// Mobile layout (default)
if (window.innerWidth <= 768) {
// Determine what to show in center (score or date)
let centerContent;
if (isFinished || isLive) {
// Show score
const homeScore = match.score_locale || '0';
const awayScore = match.score_visiteuse || '0';
centerContent = `
${homeScore} – ${awayScore}
`;
} else {
// Show date/time
const matchDate = new Date(match.date);
centerContent = `
${matchDate.toLocaleDateString(‘fr-FR’, { day: ‘numeric’, month: ‘short’ })}
${matchDate.toLocaleTimeString(‘fr-FR’, { hour: ‘2-digit’, minute: ‘2-digit’ })}
`;
}card.innerHTML += `
${match.local_team.name} ${centerContent}
${match.visitor_team.name} `;
} else {
// Desktop layout – back to original row format
card.className = ‘match-row’;
card.setAttribute(‘data-match-id’, match.id);
card.innerHTML = `
${match.local_team.name} ${(isFinished || isLive) ?
`
${match.score_locale || 0} – ${match.score_visiteuse || 0}
` :
`
${new Date(match.date).toLocaleDateString(‘fr-FR’, { day: ‘numeric’, month: ‘long’, year: ‘numeric’ })} ${new Date(match.date).toLocaleTimeString(‘fr-FR’, { hour: ‘2-digit’, minute: ‘2-digit’ })}
`
}
${match.visitor_team.name} `;
}return card;
}createMatchRow(match) {
const row = document.createElement(‘div’);
row.className = ‘match-row’;
row.onclick = () => window.location.href = `https://vibrez-rugby.com/live-en-direct/?id=${match.id}`;// Home team
const homeTeam = document.createElement(‘div’);
homeTeam.className = ‘team-info’;
homeTeam.innerHTML = `
${match.local_team.display_name} `;
row.appendChild(homeTeam);// Center (score or datetime)
const center = document.createElement(‘div’);
center.className = ‘match-center’;
if (match.etat === ‘termine’ || match.etat === ‘endirect’) {
const scoreBox = document.createElement(‘div’);
scoreBox.className = ‘score-box’ + (match.etat === ‘endirect’ ? ‘ live’ : »);
scoreBox.textContent = `${match.score_locale || 0} – ${match.score_visiteuse || 0}`;
center.appendChild(scoreBox);
} else {
const datetime = new Date(match.date);
center.innerHTML = `
${datetime.toLocaleDateString(‘fr-FR’, { day: ‘numeric’, month: ‘long’, year: ‘numeric’ })} ${datetime.toLocaleTimeString(‘fr-FR’, { hour: ‘2-digit’, minute: ‘2-digit’ })}
`;
}
row.appendChild(center);// Away team
const awayTeam = document.createElement(‘div’);
awayTeam.className = ‘team-info away’;
awayTeam.innerHTML = `
${match.visitor_team.display_name} `;
row.appendChild(awayTeam);return row;
}startAutoUpdate() {
// Update scores every 30 seconds
this.updateInterval = setInterval(() => {
this.updateLiveScores();
}, 30000);
}async updateLiveScores() {
// Only update if there are live matches
if (!this.hasLiveMatches()) return;try {
const response = await fetch(`${this.apiBase}/competitions/${this.competitionId}/live_scores`);
const liveData = await response.json();
// Update UI with new scores
this.updateScoresInUI(liveData);
} catch (error) {
console.error(‘Erreur lors de la mise à jour des scores:’, error);
}
}hasLiveMatches() {
// Check if any match is live
return true; // Simplified
}updateScoresInUI(liveData) {
// Update scores without full re-render
// Implementation depends on your needs
}showError(message) {
const mainContent = document.getElementById(‘mainContent’);
mainContent.innerHTML = `
`;
}destroy() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
if (this.cable) {
this.cable.close();
}
}
}// Initialize on page load
document.addEventListener(‘DOMContentLoaded’, () => {
const manager = new CompetitionManager();
manager.init();// Cleanup on page unload
window.addEventListener(‘beforeunload’, () => {
manager.destroy();
});
});
Résultats Pro D2 mis à jour en direct : retrouvez sur cette page les scores et les affiches de chaque journée de la deuxième division française. En effet, les résultats Pro D2 rythment chaque week-end la saison des clubs professionnels. Chaque journée livre son lot de surprises entre prétendants à la montée et équipes en lutte pour le maintien. Ainsi, cette page centralise les scores de toutes les rencontres, mis à jour instantanément après chaque coup de sifflet final.
Résultats Pro D2 : suivez tous les scores du championnat Résultats Pro D2 : comment fonctionne le système de points Les résultats Pro D2 reposent sur un système de points transparent. Ainsi, une victoire rapporte quatre points à l’équipe gagnante. Un match nul, plus rare dans le rugby professionnel, octroie deux points à chaque formation. Une défaite ne rapporte en revanche aucun point, sauf en cas de bonus défensif.
À voir également | Tous les résultats de ProD2
Par ailleurs, deux bonus viennent enrichir le décompte des points. Le bonus offensif s’obtient en marquant trois essais de plus que l’adversaire. Cette règle pousse notamment les équipes à jouer sans calcul. Le bonus défensif , quant à lui, récompense l’équipe qui perd avec un écart de sept points ou moins. De ce fait, chaque rencontre peut rapporter des points précieux pour la course au classement.
L’impact des résultats Pro D2 sur le classement Les résultats Pro D2 conditionnent l’ensemble du classement de la deuxième division. En effet, chaque victoire rapproche une équipe des phases finales . À l’inverse, une défaite peut faire reculer plusieurs formations en une seule journée. Les tableaux mis à jour sur cette page reflètent instantanément l’impact de chaque rencontre. Ainsi, les supporters suivent en direct les évolutions et anticipent les rapports de forces à venir.
De plus, la lecture des résultats Pro D2 révèle les dynamiques du championnat. Certaines équipes enchaînent parfois plusieurs victoires consécutives et grimpent rapidement dans la hiérarchie. D’autres, en revanche, traversent des périodes plus délicates et doivent réagir vite. La régularité constitue généralement la clé pour atteindre le haut du classement.
Résultats Pro D2 et course à la montée en Top 14 À l’issue du championnat, deux clubs peuvent rejoindre l’élite du rugby français. Le vainqueur de la finale Pro D2 est en effet promu directement en Top 14 . Le finaliste, quant à lui, dispute un match de barrage contre l’avant-dernier du Top 14. Ainsi, chaque victoire pendant la saison régulière peut ouvrir la porte de l’élite.
À lire aussi | Jefferson Poirot : le pilier de l’UBB mettra fin à sa carrière en fin de saison
Par conséquent, la course au top 6 mobilise plusieurs prétendants tout au long de la saison. Les résultats Pro D2 des équipes de tête sont notamment scrutés de près par les supporters. Chaque rencontre entre concurrents directs prend alors des allures de finale anticipée. En outre, les affiches de fin de saison régulière sont souvent décisives pour la qualification aux phases finales.
L’enjeu du maintien en bas de tableau À l’autre bout du classement, la lutte pour éviter la relégation en Nationale est tout aussi intense. En effet, l’équipe classée dernière est directement reléguée en Nationale . L’avant-dernier dispute quant à lui un match de barrage face au deuxième de Nationale. Cette rencontre décisive peut bouleverser le destin sportif de plusieurs clubs professionnels.
Par conséquent, les résultats Pro D2 des équipes menacées passionnent tout autant que ceux des favoris. Chaque point compte notamment lors des confrontations directes entre équipes en danger. Un bonus défensif arraché à la dernière minute peut faire toute la différence en fin de saison. Les supporters vivent alors chaque match avec une tension particulière.
Un championnat exigeant sur 30 journées Les résultats Pro D2 se construisent sur 30 journées de saison régulière. Chaque équipe affronte les quinze autres formations selon un format aller-retour. En effet, ce système garantit une équité totale entre les prétendants. Les rencontres se déroulent principalement le jeudi soir, le vendredi soir et le samedi après-midi.
De plus, la longueur de la saison exige une constance rare des joueurs et des staffs techniques. Certaines équipes puisent notamment dans leurs réserves pour tenir la distance. D’autres composent avec des blessures ou des suspensions qui pèsent sur leurs performances. Ainsi, la profondeur d’effectif devient un atout majeur au fil des mois.
Où consulter les résultats Pro D2 en direct Suivre les résultats Pro D2 n’a jamais été aussi accessible. Cette page rassemble tous les scores mis à jour en temps réel après chaque coup de sifflet final. Par ailleurs, les diffuseurs officiels proposent également des retransmissions en direct. Canal+ diffuse notamment plusieurs rencontres par journée sur ses différentes chaînes.
Pour un suivi encore plus poussé, la Ligue Nationale de Rugby publie également les scores officiels sur son site institutionnel. Les statistiques détaillées, les compositions d’équipes et les décisions disciplinaires y sont aussi consultables. Ainsi, chaque supporter dispose de toutes les données pour analyser la saison de son club favori.
Combien de clubs disputent le Pro D2 ? Le championnat Pro D2 réunit seize clubs professionnels. Ces formations s’affrontent sur 30 journées de saison régulière avant les phases finales. Chaque saison, deux nouveaux clubs sont promus depuis la Nationale pour remplacer les relégués. Ce format garantit ainsi un renouvellement régulier des effectifs et des affiches.
Quand ont lieu les matchs du Pro D2 ? Les résultats Pro D2 tombent principalement du jeudi soir au samedi soir. Une affiche débute notamment le jeudi à 21h en prime time sur Canal+. Le vendredi accueille traditionnellement plusieurs matchs à 19h et 21h. Le samedi complète enfin le programme avec des rencontres à 14h30, 15h et 21h selon les journées.
Comment se qualifie-t-on pour les phases finales ? Les six premiers du classement à l’issue des 30 journées se qualifient pour les phases finales du Pro D2. Les deux premiers accèdent directement aux demi-finales. Les équipes classées de la troisième à la sixième place disputent d’abord des barrages. Ce format récompense notamment la régularité tout au long de la saison régulière.
Quel club a remporté le plus de titres du Pro D2 ? Le palmarès du championnat Pro D2 s’enrichit chaque saison. Plusieurs clubs historiques comme Bayonne , Perpignan ou Grenoble figurent notamment parmi les vainqueurs récents. Ces formations utilisent souvent le titre de Pro D2 comme tremplin pour se relancer dans l’élite. Ainsi, la deuxième division française représente un véritable ascenseur sportif pour les clubs ambitieux.
Pour résumer Les résultats Pro D2 constituent le fil rouge de la saison de rugby professionnel français. Chaque journée redistribue en effet les cartes du classement et influence à la fois la course à la montée et la lutte pour le maintien. Grâce à un système de bonus incitatif et à un calendrier structuré, le championnat garantit du spectacle et du suspense jusqu’à la dernière journée. Cette page vous accompagne tout au long de la saison en centralisant les résultats Pro D2 mis à jour en temps réel.
Le week-end dernier a été marqué par un multiplex captivant de la Pro D2, où plusieurs équipes se sont affrontées dans des matchs décisifs pour la suite de la saison. Ce format de diffusion permet aux fans de suivre en direct les performances des équipes et de vivre des moments palpitants. Dans cet article, nous allons passer en revue les résultats clés, les performances des joueurs, ainsi que les implications de ces matchs pour le classement.
La Pro D2 est connue pour son intensité et sa compétitivité. Chaque match peut avoir un impact considérable sur le classement, et ce week-end n’a pas fait exception. Les équipes qui luttent pour la montée en Top 14 ont particulièrement besoin de points pour maintenir leurs ambitions. D’un autre côté, les clubs en bas de tableau cherchent à se sécuriser et à éviter la relégation.
Les résultats de ce multiplex mettent en lumière plusieurs joueurs qui ont su se démarquer. Par exemple, [Nom du joueur] de [Nom de l’équipe A] a été salué pour sa rapidité et sa technique. Son essai précoce a mis son équipe sur de bons rails. De même, [Nom du joueur] de [Nom de l’équipe C] a été décisif en fin de match, montrant une grande capacité à gérer la pression.
Les points obtenus lors de ces rencontres sont cruciaux. [Nom de l’équipe A] grimpe ainsi dans le classement et reste en bonne position pour la montée. En revanche, [Nom de l’équipe B], qui subit une nouvelle défaite, doit désormais s’inquiéter de sa position. Chaque point compte et peut faire la différence à la fin de la saison.
À l’issue de ce multiplex, le classement de la Pro D2 a connu quelques bouleversements. [Nom de l’équipe A] est désormais dans le haut du tableau, tandis que [Nom de l’équipe B] se retrouve dans une situation délicate. Les équipes qui luttent pour la montée doivent continuer à enchaîner les victoires, tandis que celles qui sont en bas de classement doivent se battre pour éviter la relégation.
Les prochaines journées de la Pro D2 s’annoncent tout aussi passionnantes. Les équipes devront tirer les leçons de ce week-end pour aborder les prochains matchs avec détermination. L’enjeu est de taille, et chaque match peut changer la donne.
Le multiplex de la Pro D2 de ce week-end a offert un spectacle de qualité avec des matchs serrés et des performances individuelles remarquables. Les résultats ont des implications directes sur le classement, et chaque équipe doit maintenant se concentrer sur les prochains défis. Les supporters peuvent s’attendre à des confrontations tout aussi palpitantes lors des prochaines journées.