// ==UserScript==
// @name Kraland Theme (Bundled)
// @namespace http://www.kraland.org/
// @version 1.0.1769385003464
// @description Injects the Kraland CSS theme (bundled) - Works with Tampermonkey & Violentmonkey
// @match http://www.kraland.org/*
// @run-at document-start
// @grant none
// @grant GM.xmlHttpRequest
// @connect raw.githubusercontent.com
// @compatible chrome tampermonkey
// @compatible firefox tampermonkey
// @compatible edge tampermonkey
// @compatible firefox violentmonkey
// @compatible chrome violentmonkey
// ==/UserScript==
// Main script code - CSS bundled inline
(function (){
'use strict';
// Version du userscript (sera remplacée par le build)
const CURRENT_VERSION = '1.0.1769385003464';
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Vérifie de manière sûre si le mode mobile est activé
* Protège contre l'accès à document.body avant son initialisation
*/
function isMobileMode() {
return document.body && document.body.classList && document.body.classList.contains('mobile-mode');
}
// ============================================================================
// INITIALIZATION ORCHESTRATOR
// Garantit l'ordre d'exécution séquentiel de tous les modules
// Chaque module décide lui-même s'il doit s'exécuter (mobile/desktop/page)
// ============================================================================
const InitQueue = {
_queue: [],
_initialized: false,
/**
* Enregistre une fonction d'initialisation avec sa priorité
* @param {string} name - Nom du module (pour debug)
* @param {Function} fn - Fonction d'initialisation
* @param {number} priority - Priorité (plus petit = exécuté en premier)
*/
register(name, fn, priority = 100) {
this._queue.push({ name, fn, priority });
},
/**
* Exécute toutes les fonctions enregistrées dans l'ordre de priorité
* Appelé une seule fois après le DOMContentLoaded
*/
run() {
if (this._initialized) {return;}
this._initialized = true;
// Trier par priorité (plus petit en premier)
this._queue.sort((a, b) => a.priority - b.priority);
const isMobile = isMobileMode();
// console.log(`[InitQueue] Démarrage initialisation séquentielle (${isMobile ? 'mobile' : 'desktop'})`);
// console.log('[InitQueue] Ordre:', this._queue.map(m => `${m.name}(${m.priority})`).join(' → '));
// Exécuter séquentiellement
this._queue.forEach(({ name, fn }) => {
try {
fn();
// console.log(`[InitQueue] ✓ ${name}`);
} catch (e) {
console.error(`[InitQueue] ✗ ${name}:`, e);
}
});
// console.log('[InitQueue] Initialisation terminée');
}
};
// ============================================================================
// TASK-1.1 - MOBILE DETECTION & INITIALIZATION
// ============================================================================
(function () {
// Configuration
const MOBILE_BREAKPOINT = 768; // px
// État mémorisé pour éviter les logs redondants lors des multiples appels
let previousMobileState = null;
/**
* Détecte si on est sur mobile
*/
function isMobileDevice() {
return window.innerWidth < MOBILE_BREAKPOINT;
}
/**
* Initialise le mode mobile avec détection intelligente
* Évite les logs redondants en mémorisant l'état précédent
*/
function initMobileMode() {
const currentIsMobile = isMobileDevice();
// Vérifier que document.body existe avant d'y accéder
if (!document.body) {
console.warn('[Kraland Mobile] document.body n\'est pas disponible');
return;
}
// Ne log et ne process que si l'état a changé
if (previousMobileState === currentIsMobile) {
return; // État inchangé, pas besoin de retraiter
}
previousMobileState = currentIsMobile;
if (currentIsMobile) {
document.body.classList.add('mobile-mode');
console.log('[Kraland Mobile] Mode mobile activé');
// Applique les styles critiques via JavaScript (fix Bootstrap)
applyMobileCriticalStyles();
} else {
document.body.classList.remove('mobile-mode');
console.log('[Kraland Mobile] Mode desktop');
}
}
/**
* Applique les styles critiques qui doivent surcharger Bootstrap
* Cette fonction force les styles inline pour contrer la spécificité CSS de Bootstrap
*/
function applyMobileCriticalStyles() {
// Attendre que le DOM soit prêt
const applyStyles = () => {
// Retrait padding de toutes les colonnes Bootstrap
const cols = document.querySelectorAll('[class*="col-"]');
cols.forEach(col => {
col.style.setProperty('padding-left', '0px', 'important');
col.style.setProperty('padding-right', '0px', 'important');
});
// Retrait margin des rows
const rows = document.querySelectorAll('.row');
rows.forEach(row => {
row.style.setProperty('margin-left', '0px', 'important');
row.style.setProperty('margin-right', '0px', 'important');
});
// Fix des containers
const containers = document.querySelectorAll('.container, .container-fluid');
containers.forEach(container => {
container.style.setProperty('padding-left', '0px', 'important');
container.style.setProperty('padding-right', '0px', 'important');
});
// Dashboard pleine largeur
const dashboards = document.querySelectorAll('.dashboard');
dashboards.forEach(dashboard => {
dashboard.style.setProperty('margin-left', '0px', 'important');
dashboard.style.setProperty('margin-right', '0px', 'important');
dashboard.style.setProperty('width', '100%', 'important');
dashboard.style.setProperty('padding', '0px', 'important');
});
};
// Applique immédiatement et après un court délai (pour le contenu chargé dynamiquement)
applyStyles();
setTimeout(applyStyles, 100);
setTimeout(applyStyles, 500);
}
/**
* Gère le resize de la fenêtre
*/
let resizeTimeout;
function handleResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
initMobileMode();
}, 150);
}
// Initialisation au chargement
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMobileMode);
} else {
initMobileMode();
}
// Écoute du resize
window.addEventListener('resize', handleResize);
// Export global pour debug
window.KralandMobile = {
isMobile: isMobileDevice,
reinit: initMobileMode
};
})();
// ============================================================================
// TASK-1.2 - HEADER RESPONSIVE (Bootstrap 3)
// ============================================================================
(function () {
/**
* Initialise le comportement mobile du header Bootstrap 3
* Utilise les composants natifs .navbar-toggle et .navbar-collapse
*/
function initMobileHeader() {
if (!document.body.classList.contains('mobile-mode')) {return;}
// Vérifier que Bootstrap JS est chargé
if (typeof jQuery === 'undefined' || typeof jQuery.fn.collapse === 'undefined') {
console.warn('[Mobile Header] Bootstrap JS non chargé, utilisation du fallback');
initFallbackToggle();
return;
}
// Bootstrap 3 gère automatiquement le toggle via data-toggle="collapse"
// On s'assure juste que le markup est correct
const toggle = document.querySelector('.navbar-toggle');
const collapse = document.querySelector('.navbar-collapse');
if (!toggle || !collapse) {
console.log('[Mobile Header] Éléments navbar-toggle ou navbar-collapse non trouvés');
return;
}
// Vérifier/ajouter les attributs data nécessaires pour BS3
if (!toggle.getAttribute('data-toggle')) {
toggle.setAttribute('data-toggle', 'collapse');
}
if (!toggle.getAttribute('data-target')) {
const collapseId = collapse.id || 'navbar-collapse-mobile';
collapse.id = collapseId;
toggle.setAttribute('data-target', '#' + collapseId);
}
// Auto-close menu au clic sur un lien
initMenuAutoClose();
console.log('[Mobile Header] Header Bootstrap 3 initialisé');
}
/**
* Fallback manuel si Bootstrap JS n'est pas disponible
*/
function initFallbackToggle() {
const toggle = document.querySelector('.navbar-toggle');
const collapse = document.querySelector('.navbar-collapse');
if (!toggle || !collapse) {return;}
toggle.addEventListener('click', function (e) {
e.preventDefault();
collapse.classList.toggle('in');
const expanded = collapse.classList.contains('in');
toggle.setAttribute('aria-expanded', expanded);
// Gestion du scroll
if (expanded) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
console.log('[Mobile Header] Fallback toggle initialisé');
}
/**
* Ferme le menu au clic sur un lien
*/
function initMenuAutoClose() {
const collapse = document.querySelector('.navbar-collapse');
if (!collapse) {return;}
const links = collapse.querySelectorAll('a:not(.dropdown-toggle)');
links.forEach(link => {
link.addEventListener('click', function () {
// Fermer le menu après un court délai
setTimeout(() => {
if (typeof jQuery !== 'undefined' && jQuery.fn.collapse) {
jQuery(collapse).collapse('hide');
} else {
collapse.classList.remove('in');
document.body.style.overflow = '';
}
}, 150);
});
});
}
// Enregistrer dans la queue d'initialisation mobile (priorité 10)
InitQueue.register('initMobileHeader', initMobileHeader, 10);
// Export pour debug
if (window.KralandMobile) {
window.KralandMobile.reinitHeader = initMobileHeader;
}
})();
// ============================================================================
// TASK-2.4 - DÉPLACER BOUTONS DANS HEADER MOBILE
// ============================================================================
(function () {
'use strict';
/**
* Déplace les boutons notification, kramail, palette, profil et carte dans le header mobile
* - Notification, Kramail et Palette à gauche du hamburger
* - Profil et Carte à droite du header
*/
function moveHeaderButtons() {
if (!document.body.classList.contains('mobile-mode')) {return;}
const navbarHeader = document.querySelector('.navbar-header');
const navRight = document.querySelector('.navbar-right');
if (!navbarHeader || !navRight) {
console.log('[Mobile Header Buttons] Éléments non trouvés');
return;
}
// Identifier les boutons
const allButtons = Array.from(navRight.querySelectorAll('li'));
// Trouver les boutons spécifiques par leur icône
const notificationBtn = allButtons.find(li => {
const icon = li.querySelector('.fa-bell');
return icon && !li.querySelector('.dropdown-menu');
});
const kramailBtn = allButtons.find(li => {
const icon = li.querySelector('.fa-envelope');
return icon && !li.querySelector('.dropdown-menu');
});
const mapBtn = allButtons.find(li => {
const icon = li.querySelector('.fa-globe');
return icon && !li.querySelector('.dropdown-menu');
});
if (!notificationBtn && !kramailBtn && !mapBtn) {
console.log('[Mobile Header Buttons] Aucun bouton trouvé');
return;
}
// Vérifier si déjà déplacés
if (notificationBtn && notificationBtn.hasAttribute('data-moved-to-header')) {
return;
}
// Créer le conteneur pour tous les boutons (à gauche)
const leftButtonsContainer = document.createElement('div');
leftButtonsContainer.className = 'navbar-header-buttons-left';
if (notificationBtn) {
notificationBtn.setAttribute('data-moved-to-header', 'true');
leftButtonsContainer.appendChild(notificationBtn.cloneNode(true));
}
if (kramailBtn) {
kramailBtn.setAttribute('data-moved-to-header', 'true');
leftButtonsContainer.appendChild(kramailBtn.cloneNode(true));
}
if (mapBtn) {
mapBtn.setAttribute('data-moved-to-header', 'true');
leftButtonsContainer.appendChild(mapBtn.cloneNode(true));
}
// Insérer le conteneur dans le header
if (leftButtonsContainer.children.length > 0) {
navbarHeader.insertBefore(leftButtonsContainer, navbarHeader.firstChild);
}
// Cacher le logo en mobile
const navbarBrand = document.querySelector('.navbar-brand');
if (navbarBrand) {
navbarBrand.style.display = 'none';
}
console.log('[Mobile Header Buttons] Boutons déplacés (notification, kramail, map)');
}
// Enregistrer dans la queue d'initialisation mobile (priorité 20)
InitQueue.register('moveHeaderButtons', moveHeaderButtons, 20);
})();
// ============================================================================
// TASK-2.5 - DÉPLACER LE LOGO AU-DESSUS DU BLOC BIENVENU
// ============================================================================
(function () {
'use strict';
/**
* [DÉSACTIVÉ] Le logo reste maintenant dans la navbar à gauche
* Ancienne fonction qui déplaçait le logo sous la navbar
*/
/*
function moveLogoToWelcomeBlock() {
if (!document.body.classList.contains('mobile-mode')) return;
// Trouver le logo
const logo = document.querySelector('.navbar-brand');
if (!logo) {
console.log('[Mobile Logo] Logo non trouvé');
return;
}
// Vérifier si déjà déplacé
if (document.querySelector('[data-moved-below-navbar]')) {
return;
}
// Trouver la navbar
const navbar = document.querySelector('.navbar');
if (!navbar) {
console.log('[Mobile Logo] Navbar non trouvée');
return;
}
// Cloner le logo et le rendre visible
const logoClone = logo.cloneNode(true);
logoClone.style.display = 'flex';
logoClone.setAttribute('data-moved-below-navbar', 'true');
// Insérer le logo juste après la navbar
if (navbar.parentElement) {
navbar.parentElement.insertBefore(logoClone, navbar.nextSibling);
}
console.log('[Mobile Logo] Logo déplacé sous la navbar');
}
// Initialiser au chargement du DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(moveLogoToWelcomeBlock, 200);
});
} else {
setTimeout(moveLogoToWelcomeBlock, 200);
}
*/
})();
// ============================================
// TASK-1.4 - MINI-PROFIL COLLAPSIBLE
// ============================================
(function () {
'use strict';
/**
* Extrait les données d'une jauge (valeur/max)
*/
function extractGaugeData(element, type) {
const text = element.textContent;
const match = text.match(/(\d+)\s*\/\s*(\d+)|(\d+)\s+\/\s+(\d+)/);
if (!match) {
// Chercher juste le nombre si pas de format complet
const numMatch = text.match(/\d+/);
if (numMatch) {
const val = parseInt(numMatch[0]);
return { type, current: val, max: val, percent: 100 };
}
return { type, current: 0, max: 1, percent: 0 };
}
const current = parseInt(match[1] || match[3]);
const max = parseInt(match[2] || match[4]);
const percent = Math.min(100, (current / max) * 100);
return { type, current, max, percent };
}
/**
* Trouve les données du profil joueur
*/
function findProfileData() {
// Container principal
const profileSection = document.getElementById('player-header-section');
const mainPanel = document.getElementById('player-main-panel');
if (!profileSection || !mainPanel) {
console.warn('[Mobile Mini-Profile] Sections profil non trouvées');
return null;
}
// Nom du joueur (dans le header)
const nameElement = profileSection.querySelector('.list-group-item.active');
const playerName = nameElement ? nameElement.textContent.replace('×', '').trim() : 'Joueur';
// Avatar (dans la première row)
const avatarLink = mainPanel.querySelector('.btn.alert100 img, a[href*="perso"] img, img[src*="avatar"]');
const avatarSrc = avatarLink ? avatarLink.src : null;
// Argent (chercher div.mini.t avec icône fa-coins)
let moneyElement = null;
const moneyContainers = document.querySelectorAll('div.mini.t');
for (const container of moneyContainers) {
if (container.querySelector('i.fa.fa-coins') && container.textContent.includes('MØ')) {
moneyElement = container;
break;
}
}
const money = moneyElement ? moneyElement.textContent.trim() : '0 MØ';
// Horloge (dans player-vitals-section)
const vitalsSection = document.getElementById('player-vitals-section');
const clockElement = vitalsSection ? vitalsSection.querySelector('.c100') : null;
const clock = clockElement ? clockElement.textContent.trim() : '--:--';
// Jauges PV/PM/PP - Nouvelle approche
// Chercher les éléments contenant "PV", "PM", "PP" avec leur valeur
const findGaugeInSection = (section, type) => {
if (!section) {return null;}
// Chercher l'élément contenant le type (PV, PM, PP)
const elements = Array.from(section.querySelectorAll('*'));
const gaugeEl = elements.find(el => {
const text = el.textContent.trim();
const hasType = text.startsWith(type) || text.includes(` ${type} `);
const hasNumber = /\d+/.test(text);
return hasType && hasNumber && el.children.length <= 2;
});
if (!gaugeEl) {return null;}
// Extraire la valeur actuelle et max
const text = gaugeEl.textContent.trim();
const match = text.match(/(\d+)\s*\/\s*(\d+)/);
if (match) {
// Format "27 / 27"
return extractGaugeData(gaugeEl, type.toLowerCase());
} else {
// Format "PV 27" sans max - chercher la barre de progression pour le max
const valueMatch = text.match(/\d+/);
if (!valueMatch) {return null;}
const current = parseInt(valueMatch[0]);
// Chercher la barre de progression associée
const progressBar = gaugeEl.querySelector('.progress-bar, [class*="bar"]');
let max = current; // Par défaut, considérer que c'est plein
if (progressBar) {
const width = progressBar.style.width;
if (width && width.includes('%')) {
const percent = parseInt(width);
if (percent > 0) {
max = Math.round(current * 100 / percent);
}
}
}
const percent = max > 0 ? Math.min(100, (current / max) * 100) : 0;
return { type: type.toLowerCase(), current, max, percent };
}
};
const gaugesPV = findGaugeInSection(vitalsSection, 'PV');
const gaugesPM = findGaugeInSection(vitalsSection, 'PM');
const gaugesPP = findGaugeInSection(vitalsSection, 'PP');
return {
name: playerName,
avatar: avatarSrc,
money: money,
clock: clock,
gauges: {
pv: gaugesPV,
pm: gaugesPM,
pp: gaugesPP
}
};
}
/**
* Crée le mini-profil mobile
*/
function createMiniProfile() {
if (!document.body.classList.contains('mobile-mode')) {return;}
if (document.querySelector('.mobile-mini-profile')) {return;}
const profileData = findProfileData();
if (!profileData) {
console.warn('[Mobile Mini-Profile] Données profil non trouvées');
return;
}
// Container principal
const miniProfile = document.createElement('div');
miniProfile.className = 'mobile-mini-profile collapsed';
miniProfile.setAttribute('data-task', '1.4');
// Header (toujours visible)
const header = document.createElement('div');
header.className = 'mobile-mini-profile-header';
if (profileData.avatar) {
const avatar = document.createElement('img');
avatar.src = profileData.avatar;
avatar.className = 'avatar';
avatar.alt = 'Avatar';
header.appendChild(avatar);
}
const info = document.createElement('div');
info.className = 'mobile-mini-profile-info';
const nameRow = document.createElement('div');
nameRow.className = 'mobile-mini-profile-name-row';
const name = document.createElement('div');
name.className = 'mobile-mini-profile-name';
name.textContent = profileData.name;
nameRow.appendChild(name);
// Bouton Gestion du personnage
const manageBtn = document.createElement('a');
manageBtn.href = '#';
manageBtn.className = 'btn btn-default alert100 mobile-mini-profile-manage-btn';
manageBtn.innerHTML = '⚙️ ';
manageBtn.setAttribute('data-toggle', 'tooltip');
manageBtn.setAttribute('data-placement', 'bottom');
manageBtn.setAttribute('title', 'Gestion du personnage');
nameRow.appendChild(manageBtn);
info.appendChild(nameRow);
const moneyRow = document.createElement('div');
moneyRow.className = 'mobile-mini-profile-money';
moneyRow.innerHTML = `
${profileData.money}
⏱️ ${profileData.clock}
`;
info.appendChild(moneyRow);
header.appendChild(info);
miniProfile.appendChild(header);
// Jauges compactes (toujours visibles)
const gaugesCompact = document.createElement('div');
gaugesCompact.className = 'mobile-mini-profile-gauges-compact';
['pv', 'pm', 'pp'].forEach(type => {
const gauge = profileData.gauges[type];
if (!gauge) {return;}
const el = document.createElement('div');
el.className = 'mobile-gauge-compact';
el.innerHTML = `
${type.toUpperCase()}
${gauge.current}
`;
gaugesCompact.appendChild(el);
});
miniProfile.appendChild(gaugesCompact);
// Détails (masqués par défaut)
const details = document.createElement('div');
details.className = 'mobile-mini-profile-details';
// Jauges détaillées
const gaugesFull = document.createElement('div');
gaugesFull.className = 'mobile-mini-profile-gauges-full';
['pv', 'pm', 'pp'].forEach(type => {
const gauge = profileData.gauges[type];
if (!gauge) {return;}
const el = document.createElement('div');
el.className = 'mobile-gauge-full';
el.innerHTML = `
`;
gaugesFull.appendChild(el);
});
details.appendChild(gaugesFull);
// Caractéristiques (FOR, VOL, CHA, GES, INT, PER)
const characteristicsContainer = document.createElement('div');
characteristicsContainer.className = 'mobile-mini-profile-characteristics';
const statsSection = document.getElementById('col-leftest-stats');
if (statsSection) {
// Chercher les 6 premières caractéristiques (alert121 à alert126)
const charElements = [];
for (let i = 121; i <= 126; i++) {
const char = statsSection.querySelector(`.alert${i}`);
if (char) {
charElements.push(char);
}
}
if (charElements.length > 0) {
charElements.forEach(charEl => {
const charClone = charEl.cloneNode(true);
charClone.className = charEl.className + ' mobile-characteristic-badge';
charClone.style.cssText = 'min-width: 44px; min-height: 44px; margin: 4px;';
characteristicsContainer.appendChild(charClone);
});
details.appendChild(characteristicsContainer);
}
}
// Compétences
const skillsContainer = document.createElement('div');
skillsContainer.className = 'mobile-mini-profile-skills';
const skillsPanel = document.getElementById('skills-panel');
if (skillsPanel) {
// Récupérer toutes les compétences (alert111 à alert1118)
const skills = skillsPanel.querySelectorAll('.ds_game[class*="alert11"]');
if (skills.length > 0) {
skills.forEach(skillEl => {
const skillClone = skillEl.cloneNode(true);
skillClone.className = skillEl.className + ' mobile-skill-item';
skillClone.style.cssText = 'min-height: 44px; margin: 4px; display: flex; align-items: center; justify-content: center;';
skillsContainer.appendChild(skillClone);
});
details.appendChild(skillsContainer);
}
}
miniProfile.appendChild(details);
// Toggle expand/collapse
miniProfile.addEventListener('click', (e) => {
// Ne pas toggler si on clique sur un élément interactif (bouton, lien)
if (e.target.closest('.mobile-mini-profile-avatar-btn, .mobile-characteristic-badge, .mobile-skill-item')) {
return;
}
miniProfile.classList.toggle('collapsed');
miniProfile.classList.toggle('expanded');
console.log('[Mobile Mini-Profile] État:',
miniProfile.classList.contains('expanded') ? 'déplié' : 'replié'
);
});
// Insérer après la navbar (élément préexistant) ou au début du body
// Note: On utilise .navbar (DOM original) et non .mobile-tab-bar (créé par le script)
const navbar = document.querySelector('.navbar');
const container = document.getElementById('content') || document.body;
if (navbar && navbar.nextSibling) {
navbar.parentNode.insertBefore(miniProfile, navbar.nextSibling);
} else {
container.insertBefore(miniProfile, container.firstChild);
}
console.log('[Mobile Mini-Profile] Créé avec succès');
console.log(' - Nom:', profileData.name);
console.log(' - Argent:', profileData.money);
console.log(' - Horloge:', profileData.clock);
console.log(' - PV:', profileData.gauges.pv ? `${profileData.gauges.pv.current}/${profileData.gauges.pv.max}` : 'N/A');
}
/**
* Initialise le mini-profil
*/
function initMiniProfile() {
if (!document.body.classList.contains('mobile-mode')) {return;}
createMiniProfile();
}
// Enregistrer dans la queue d'initialisation mobile (priorité 40 - après tab bar)
InitQueue.register('initMiniProfile', initMiniProfile, 40);
// Exposer pour debug
if (window.KralandMobile) {
window.KralandMobile.initMiniProfile = initMiniProfile;
}
})();
// ============================================
// TASK-1.5 - ACTIONS RAPIDES HORIZONTALES (Bootstrap 3)
// ============================================
(function () {
'use strict';
/**
* Crée la barre d'actions rapides mobile avec Bootstrap 3
*/
function createQuickActions() {
if (!document.body.classList.contains('mobile-mode')) {return;}
if (document.querySelector('.mobile-quick-actions')) {return;}
// Vérifier qu'on est bien sur une page /jouer/*
if (!window.location.pathname.startsWith('/jouer/')) {
return;
}
// Trouver la section actions originale
const actionsSection = document.getElementById('player-actions-section');
if (!actionsSection) {
console.warn('[Mobile Quick Actions] Section actions non trouvée');
return;
}
// Récupérer les boutons originaux
const originalButtons = actionsSection.querySelectorAll('a.btn, button.btn');
if (originalButtons.length === 0) {
console.warn('[Mobile Quick Actions] Aucun bouton trouvé');
return;
}
// Container avec btn-group-justified Bootstrap 3
const container = document.createElement('div');
container.className = 'btn-group btn-group-justified mobile-quick-actions';
container.setAttribute('role', 'group');
container.setAttribute('data-task', '1.5');
// Cloner chaque bouton avec structure Bootstrap 3 justified
originalButtons.forEach((originalBtn) => {
// Wrapper btn-group requis par BS3 justified
const btnGroup = document.createElement('div');
btnGroup.className = 'btn-group';
btnGroup.setAttribute('role', 'group');
// Cloner le bouton
const btn = originalBtn.cloneNode(true);
btn.classList.add('mobile-quick-action');
// Extraire l'icône et le label
const icon = btn.querySelector('i');
const label = btn.textContent.trim();
// Reconstruire le contenu avec structure mobile
btn.innerHTML = '';
if (icon) {
const iconClone = icon.cloneNode(true);
iconClone.classList.add('mobile-quick-action-icon');
btn.appendChild(iconClone);
}
const labelSpan = document.createElement('span');
labelSpan.className = 'mobile-quick-action-label';
labelSpan.textContent = label;
btn.appendChild(labelSpan);
btnGroup.appendChild(btn);
container.appendChild(btnGroup);
});
// Insérer après la navbar (élément préexistant)
// Note: On utilise .navbar (DOM original) et non .mobile-mini-profile/.mobile-tab-bar (créés par le script)
const navbar = document.querySelector('.navbar');
if (navbar && navbar.nextSibling) {
navbar.parentNode.insertBefore(container, navbar.nextSibling);
} else {
document.body.insertBefore(container, document.body.firstChild);
}
console.log('[Mobile Quick Actions] Créées avec', originalButtons.length, 'actions');
}
/**
* Initialise les actions rapides
*/
function initQuickActions() {
if (!document.body.classList.contains('mobile-mode')) {return;}
createQuickActions();
}
// Enregistrer dans la queue d'initialisation mobile (priorité 50 - après mini-profil)
InitQueue.register('initQuickActions', initQuickActions, 50);
// Exposer pour debug
if (window.KralandMobile) {
window.KralandMobile.initQuickActions = initQuickActions;
}
})();
// ============================================
// TASK-1.6 - HOMEPAGE CAROUSEL REMOVAL (Mobile)
// ============================================
(function () {
/**
* Supprime le carousel Bootstrap sur la page d'accueil en mode mobile
* Le carousel prend trop de place et n'est pas adapté aux petits écrans
*/
function removeHomepageCarousel() {
// Ne s'exécute qu'en mode mobile
if (!isMobileMode()) {return;}
// Ne s'exécute que sur la page d'accueil
const isHomePage = window.location.pathname === '/' ||
window.location.pathname === '/accueil' ||
window.location.pathname.endsWith('/');
if (!isHomePage) {return;}
// Sélectionner le carousel Bootstrap (.carousel)
const carousel = document.querySelector('.carousel');
if (!carousel) {
console.log('[Homepage Carousel] Carousel non trouvé sur la page');
return;
}
// Supprimer le carousel du DOM
carousel.remove();
console.log('[Homepage Carousel] Carousel supprimé en mode mobile');
}
// Enregistrer dans InitQueue avec priorité 5 (après initMobileMode à priorité 0)
InitQueue.register('removeHomepageCarousel', removeHomepageCarousel, 5);
// Exposer pour debug
if (window.KralandMobile) {
window.KralandMobile.removeHomepageCarousel = removeHomepageCarousel;
}
})();
// ============================================
// TASK-1.3 - TAB BAR NAVIGATION (Bootstrap 3)
// ============================================
(function () {
'use strict';
/**
* Trouve les liens du menu navigation jeu
*/
function findNavigationLinks() {
// Patterns à chercher avec icônes Font Awesome
const patterns = [
{ pattern: '/jouer/plateau', href: '/jouer/plateau', label: 'Agir', icon: 'fa-bolt' },
{ pattern: '/jouer/materiel', href: '/jouer/materiel', label: 'Matériel', icon: 'fa-cube' },
{ pattern: '/jouer/perso', href: '/jouer/perso', label: 'Personnage', icon: 'fa-user' },
{ pattern: '/jouer/bat', href: '/jouer/bat', label: 'Bâtiments', icon: 'fa-home' },
{ pattern: '/jouer/pnj', href: '/jouer/pnj', label: 'Employés', icon: 'fa-users' }
];
const links = [];
patterns.forEach(item => {
const link = document.querySelector(`a[href*="${item.pattern}"]`);
// Créer le lien même s'il n'existe pas sur la page actuelle
links.push({
href: link ? link.getAttribute('href') : item.href,
text: link ? (link.textContent.trim() || item.label) : item.label,
pattern: item.pattern,
icon: item.icon
});
});
return links;
}
/**
* Crée la tab bar avec structure Bootstrap 3
*/
function createTabBar() {
if (!document.body.classList.contains('mobile-mode')) {return;}
if (document.querySelector('.mobile-tab-bar')) {return;} // Déjà créé
// Vérifier qu'on est bien sur une page /jouer/*
if (!window.location.pathname.startsWith('/jouer/')) {
return;
}
// Trouver les liens
const navLinks = findNavigationLinks();
if (navLinks.length === 0) {
console.warn('[Mobile Tab Bar] Aucun lien de navigation disponible');
return;
}
// Créer la tab bar avec structure Bootstrap 3 (ul.nav.nav-tabs)
const tabBar = document.createElement('ul');
tabBar.className = 'nav nav-tabs mobile-tab-bar';
tabBar.setAttribute('role', 'tablist');
// Créer les tabs (li > a comme dans BS3) avec icônes + texte
navLinks.forEach(linkData => {
const li = document.createElement('li');
li.setAttribute('role', 'presentation');
const a = document.createElement('a');
a.href = linkData.href;
a.setAttribute('role', 'tab');
// Créer structure icône + texte
const icon = document.createElement('i');
icon.className = `fa ${linkData.icon} mobile-tab-icon`;
const label = document.createElement('span');
label.className = 'mobile-tab-label';
label.textContent = linkData.text;
a.appendChild(icon);
a.appendChild(label);
// Marquer l'onglet actif (classe sur le li comme dans BS3)
const currentPath = window.location.pathname;
if (currentPath.includes(linkData.pattern)) {
li.classList.add('active');
a.setAttribute('aria-selected', 'true');
} else {
a.setAttribute('aria-selected', 'false');
}
li.appendChild(a);
tabBar.appendChild(li);
});
// Insérer après le header
const header = document.querySelector('.navbar') ||
document.querySelector('header') ||
document.body.firstElementChild;
if (header && header.nextSibling) {
header.parentNode.insertBefore(tabBar, header.nextSibling);
} else {
document.body.insertBefore(tabBar, document.body.firstChild);
}
// Gérer l'indicateur de scroll
handleTabBarScroll(tabBar);
// Scroll automatique vers l'onglet actif
setTimeout(() => scrollToActiveTab(tabBar), 100);
console.log('[Mobile Tab Bar] Créée avec', navLinks.length, 'onglets');
}
/**
* Gère l'indicateur de scroll de la tab bar
*/
function handleTabBarScroll(tabBar) {
const checkScroll = () => {
const isAtEnd = tabBar.scrollLeft + tabBar.clientWidth >= tabBar.scrollWidth - 5;
tabBar.classList.toggle('scrolled-end', isAtEnd);
};
tabBar.addEventListener('scroll', checkScroll);
// Check initial
setTimeout(checkScroll, 100);
// Re-check au resize
window.addEventListener('resize', checkScroll);
}
/**
* Scroll automatique vers l'onglet actif
*/
function scrollToActiveTab(tabBar) {
const activeTab = tabBar.querySelector('li.active > a');
if (!activeTab) {return;}
// Scroll smooth vers l'onglet actif
const tabBarRect = tabBar.getBoundingClientRect();
const activeRect = activeTab.getBoundingClientRect();
const scrollLeft = activeRect.left - tabBarRect.left - (tabBarRect.width / 2) + (activeRect.width / 2);
tabBar.scrollTo({
left: tabBar.scrollLeft + scrollLeft,
behavior: 'smooth'
});
}
/**
* Initialise la tab bar
*/
function initTabBar() {
if (!document.body.classList.contains('mobile-mode')) {return;}
createTabBar();
}
// Ajouter la fonction à l'API globale
if (window.KralandMobile) {
window.KralandMobile.initTabBar = initTabBar;
}
// Enregistrer dans la queue d'initialisation mobile (priorité 30 - après header buttons)
InitQueue.register('initTabBar', initTabBar, 30);
})();
// ============================================================================
// TASK-2.1 - MÉMORISATION DES ALERTES FERMÉES
// ============================================================================
(function () {
const STORAGE_KEY = 'kraland_dismissed_alerts';
/**
* Génère un hash simple à partir d'une chaîne
*/
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash).toString(36);
}
/**
* Récupère la liste des alertes fermées depuis le localStorage
*/
function getDismissedAlerts() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Alerts Memory] Erreur lecture localStorage:', e);
return [];
}
}
/**
* Sauvegarde une alerte fermée dans le localStorage
*/
function saveDismissedAlert(alertId) {
try {
const dismissed = getDismissedAlerts();
if (!dismissed.includes(alertId)) {
dismissed.push(alertId);
localStorage.setItem(STORAGE_KEY, JSON.stringify(dismissed));
console.log('[Alerts Memory] Alerte mémorisée:', alertId);
}
} catch (e) {
console.warn('[Alerts Memory] Erreur sauvegarde localStorage:', e);
}
}
/**
* Génère un identifiant unique pour une alerte basé sur son contenu
*/
function getAlertId(alert) {
// Essayer d'utiliser un ID existant
if (alert.id) {return alert.id;}
// Sinon, créer un hash du contenu texte (sans les espaces multiples)
const text = alert.textContent.trim().replace(/\s+/g, ' ');
return 'alert_' + simpleHash(text);
}
/**
* Cache automatiquement les alertes déjà fermées
*/
function hideRememberedAlerts() {
const dismissedAlerts = getDismissedAlerts();
if (dismissedAlerts.length === 0) {return;}
const alerts = document.querySelectorAll('.alert.alert-dismissible');
let hiddenCount = 0;
alerts.forEach(alert => {
const alertId = getAlertId(alert);
if (dismissedAlerts.includes(alertId)) {
alert.style.display = 'none';
hiddenCount++;
}
});
if (hiddenCount > 0) {
console.log(`[Alerts Memory] ${hiddenCount} alerte(s) masquée(s) automatiquement`);
}
}
/**
* Écoute les fermetures d'alertes et les mémorise
*/
function watchAlertDismissal() {
const alerts = document.querySelectorAll('.alert.alert-dismissible');
alerts.forEach(alert => {
// Trouver le bouton de fermeture
const closeBtn = alert.querySelector('.close, [data-dismiss="alert"]');
if (!closeBtn) {return;}
// Écouter le clic sur le bouton de fermeture
closeBtn.addEventListener('click', function () {
const alertId = getAlertId(alert);
saveDismissedAlert(alertId);
});
});
console.log(`[Alerts Memory] Surveillance activée pour ${alerts.length} alerte(s)`);
}
/**
* Initialise la mémorisation des alertes
*/
function initAlertsMemory() {
// D'abord cacher les alertes déjà fermées
hideRememberedAlerts();
// Puis surveiller les nouvelles fermetures
watchAlertDismissal();
}
// Initialiser au chargement du DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAlertsMemory);
} else {
initAlertsMemory();
}
/**
* Ajoute un bouton de réinitialisation dans la page profil/interface
*/
function addResetButtonToInterfacePage() {
// Vérifier qu'on est sur la bonne page
if (!window.location.pathname.includes('/profil/interface')) {return;}
// Attendre que le formulaire Tampermonkey soit présent
const checkForm = setInterval(() => {
const tamperForm = document.querySelector('#kr-tamper-theme-form');
if (!tamperForm) {return;}
clearInterval(checkForm);
// Créer une section dédiée pour le bouton
const section = document.createElement('div');
section.className = 'form-group';
section.style.marginTop = '20px';
section.style.paddingTop = '15px';
section.style.borderTop = '1px solid rgba(255, 255, 255, 0.1)';
// Créer le label
const label = document.createElement('label');
label.className = 'col-sm-3 control-label';
label.style.paddingLeft = '0px';
label.style.paddingRight = '0px';
label.textContent = 'Alertes';
// Créer le conteneur du bouton
const btnContainer = document.createElement('div');
btnContainer.className = 'col-sm-9';
btnContainer.style.paddingLeft = '0px';
// Créer le bouton de réinitialisation
const resetBtn = document.createElement('button');
resetBtn.type = 'button';
// Taille réduite : 1/3 de largeur, affichage inline pour ne pas être full-width
resetBtn.className = 'btn btn-warning kr-reset-alerts-btn';
resetBtn.style.width = '33.333%';
resetBtn.style.display = 'inline-block';
// Ajouter l'icône et le texte
const icon = document.createElement('span');
icon.className = 'glyphicon glyphicon-refresh';
icon.style.marginRight = '5px';
const text = document.createTextNode('Réinitialiser les alertes fermées');
resetBtn.appendChild(icon);
resetBtn.appendChild(text);
// Action au clic
resetBtn.addEventListener('click', function () {
const dismissed = getDismissedAlerts();
const count = dismissed.length;
if (count === 0) {
alert('Aucune alerte fermée à réinitialiser.');
return;
}
if (confirm(`Voulez-vous vraiment réinitialiser ${count} alerte(s) fermée(s) ? Elles réapparaîtront lors du prochain chargement de page.`)) {
localStorage.removeItem(STORAGE_KEY);
console.log('[Alerts Memory] Alertes mémorisées effacées');
// Feedback visuel
icon.className = 'glyphicon glyphicon-ok';
resetBtn.textContent = '';
resetBtn.appendChild(icon);
resetBtn.appendChild(document.createTextNode(' ' + (count === 1 ? '1 alerte réinitialisée !' : `${count} alertes réinitialisées !`)));
// Conserver la classe utilitaire pour garder la largeur réduite
resetBtn.className = 'btn btn-success kr-reset-alerts-btn';
// Mettre à jour le texte d'aide pour refléter le nouvel état
updateHelpText();
setTimeout(() => {
icon.className = 'glyphicon glyphicon-refresh';
resetBtn.textContent = '';
resetBtn.appendChild(icon);
resetBtn.appendChild(document.createTextNode(' Réinitialiser les alertes fermées'));
resetBtn.className = 'btn btn-warning btn-block';
}, 3000);
}
});
// Ajouter une description (placée sur la même ligne que le bouton)
const helpText = document.createElement('p');
helpText.className = 'help-block';
// Afficher sur la même ligne que le bouton et aligner verticalement
helpText.style.display = 'inline-block';
helpText.style.marginTop = '0';
helpText.style.marginLeft = '10px';
helpText.style.verticalAlign = 'middle';
helpText.style.fontSize = '12px';
helpText.style.opacity = '0.7';
// Helper pour la gestion du singulier/pluriel
function formatDismissedCount(n) {
if (n === 0) return 'Aucune alerte actuellement masquée';
if (n === 1) return '1 alerte actuellement masquée';
return `${n} alertes actuellement masquées`;
}
function updateHelpText() {
helpText.textContent = formatDismissedCount(getDismissedAlerts().length);
}
// Initialiser le texte
updateHelpText();
// Assembler la section
btnContainer.appendChild(resetBtn);
btnContainer.appendChild(helpText);
section.appendChild(label);
section.appendChild(btnContainer);
// Insérer après le formulaire Tampermonkey
tamperForm.parentNode.insertBefore(section, tamperForm.nextSibling);
console.log('[Alerts Memory] Bouton de réinitialisation ajouté');
}, 100);
}
// Initialiser au chargement du DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAlertsMemory);
document.addEventListener('DOMContentLoaded', addResetButtonToInterfacePage);
} else {
initAlertsMemory();
addResetButtonToInterfacePage();
}
// Exposer une fonction pour réinitialiser (debug)
if (window.KralandMobile) {
window.KralandMobile.clearDismissedAlerts = function () {
localStorage.removeItem(STORAGE_KEY);
console.log('[Alerts Memory] Alertes mémorisées effacées');
};
}
})();
// ============================================================================
// TASK-2.3 - MESSAGE DE BIENVENUE SUR PAGE D'ACCUEIL
// ============================================================================
(function () {
'use strict';
/**
* Ajoute "Bienvenu " avant le nom de l'utilisateur sur la page d'accueil
*/
function addWelcomeMessage() {
// Vérifier qu'on est sur la page d'accueil
const path = window.location.pathname;
if (path !== '/' && path !== '/accueil' && !path.startsWith('/accueil')) {
return;
}
// Chercher le h4 qui contient le nom de l'utilisateur
// C'est un h4.list-group-item-heading.count qui n'est pas un nombre
const allH4 = Array.from(document.querySelectorAll('h4.list-group-item-heading.count'));
const userNameH4 = allH4.find(h4 => {
const text = h4.textContent.trim();
// Le nom d'utilisateur n'est pas un nombre
return !/^\d+$/.test(text) && text.length > 0;
});
if (!userNameH4) {
console.log('[Welcome Message] Nom d\'utilisateur non trouvé');
return;
}
const userName = userNameH4.textContent.trim();
// Vérifier que "Bienvenu" n'est pas déjà présent
if (userName.startsWith('Bienvenu')) {
return;
}
// Ajouter "Bienvenu " avant le nom
userNameH4.textContent = 'Bienvenu ' + userName;
console.log('[Welcome Message] Message de bienvenue ajouté pour:', userName);
}
// Initialiser au chargement du DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addWelcomeMessage);
} else {
addWelcomeMessage();
}
})();
// ============================================================================
// TASK-2.2 - ACCORDÉON GROUPES (Base)
// ============================================================================
(function () {
/**
* Transforme les sections de groupes en accordéon collapsible
* Structure DOM identifiée :
* - .dashboard-section : Container de chaque groupe
* - .dashboard-section-mygroup : Mon groupe (toujours visible)
* - .dashboard-section-header : Header avec titre du groupe
* - .dashboard-group-title : Nom du leader
* - .dashboard-cards-grid : Grille des membres (à masquer/afficher)
*/
/**
* Rend un groupe collapsible
*/
function makeGroupCollapsible(section, isMyGroup) {
const header = section.querySelector('.dashboard-section-header');
const cardsGrid = section.querySelector('.dashboard-cards-grid');
if (!header || !cardsGrid) {return;}
// Ajouter la classe accordion au header
header.classList.add('dashboard-section-header-accordion');
// État initial : mon groupe ouvert, autres fermés
const isExpanded = isMyGroup;
cardsGrid.classList.toggle('collapsed', !isExpanded);
header.classList.toggle('expanded', isExpanded);
// Ajouter l'icône d'expansion
const icon = document.createElement('i');
icon.className = 'fa fa-chevron-down accordion-icon';
header.appendChild(icon);
// Gérer le clic
header.style.cursor = 'pointer';
header.addEventListener('click', (e) => {
// Ne pas intercepter les clics sur les boutons d'action
if (e.target.closest('.dashboard-group-buttons')) {return;}
// Toggle l'état
const isNowExpanded = !cardsGrid.classList.contains('collapsed');
cardsGrid.classList.toggle('collapsed', isNowExpanded);
header.classList.toggle('expanded', !isNowExpanded);
console.log('[Group Accordion]',
section.querySelector('.dashboard-group-title')?.textContent,
isNowExpanded ? 'collapsed' : 'expanded'
);
});
console.log('[Group Accordion] Groupe configuré:',
section.querySelector('.dashboard-group-title')?.textContent,
'état initial:', isExpanded ? 'ouvert' : 'fermé'
);
}
/**
* Initialise l'accordéon pour tous les groupes
*/
function initGroupsAccordion() {
if (!document.body.classList.contains('mobile-mode')) {return;}
const sections = document.querySelectorAll('.dashboard-section');
let count = 0;
sections.forEach(section => {
const header = section.querySelector('.dashboard-section-header');
const title = section.querySelector('.dashboard-group-title');
// Vérifier que c'est bien un groupe (a un header avec titre)
if (header && title) {
const isMyGroup = section.classList.contains('dashboard-section-mygroup');
makeGroupCollapsible(section, isMyGroup);
count++;
}
});
console.log('[Groups Accordion] Initialisé pour', count, 'groupes');
}
// Ajouter la fonction à l'API globale
if (window.KralandMobile) {
window.KralandMobile.initGroupsAccordion = initGroupsAccordion;
}
// Enregistrer dans la queue d'initialisation mobile (priorité 70)
InitQueue.register('initGroupsAccordion', initGroupsAccordion, 70);
})();
// ============================================================================
// TASK-2.5 : Commerce - Accordéon catégories
// ============================================================================
// Enregistrer l'initialisation du Commerce Accordion dans la queue pour éviter l'accès à document.body avant DOMContentLoaded
InitQueue.register('Commerce Accordion', function initCommerceAccordion() {
if (!isMobileMode()) {return;}
if (!window.location.href.includes('jouer/plateau')) {return;}
const categories = ['Nourriture', 'Repas', 'Boissons', 'Bons d\'état / Loterie', 'Services'];
const categoryDivs = [];
// Trouver tous les divs de catégorie
document.querySelectorAll('h4.list-group-item-heading').forEach(h4 => {
const categoryName = h4.textContent.trim();
if (categories.includes(categoryName)) {
const categoryDiv = h4.parentElement;
if (categoryDiv && categoryDiv.classList.contains('list-group-item')) {
categoryDivs.push({
name: categoryName,
div: categoryDiv,
h4: h4
});
}
}
});
if (categoryDivs.length === 0) {return;}
console.log(`[Commerce Accordion] Trouvé ${categoryDivs.length} catégories`);
// Pour chaque catégorie, trouver ses produits (les qui suivent jusqu'à la prochaine catégorie)
categoryDivs.forEach((category, index) => {
const products = [];
let currentElement = category.div.nextElementSibling;
// Parcourir les éléments suivants jusqu'à la prochaine catégorie
while (currentElement) {
// Vérifier que l'élément a bien une propriété classList (éléments HTML uniquement)
if (!currentElement.classList) {
currentElement = currentElement.nextElementSibling;
continue;
}
// Si on trouve une autre catégorie, on s'arrête
if (currentElement.classList.contains('ds_forum') &&
currentElement.querySelector('h4.list-group-item-heading')) {
break;
}
// Si c'est un produit (lien avec classe ds_game)
if (currentElement.tagName === 'A' && currentElement.classList.contains('ds_game')) {
products.push(currentElement);
}
currentElement = currentElement.nextElementSibling;
}
// Créer un conteneur pour les produits
const productsContainer = document.createElement('div');
productsContainer.className = 'commerce-products-container';
// Déplacer les produits dans le conteneur
products.forEach(product => {
productsContainer.appendChild(product);
});
// Vérifier que le div de catégorie a bien un parent et que les éléments existent
if (!category.div || !category.div.parentElement || !category.h4) {
console.warn(`[Commerce Accordion] ${category.name}: éléments manquants`);
return;
}
// Insérer le conteneur après le div de catégorie
category.div.parentElement.insertBefore(productsContainer, category.div.nextSibling);
// Ajouter la classe accordion au div de catégorie
category.div.classList.add('commerce-category-header');
// État initial : première catégorie (Nourriture) ouverte
const isExpanded = index === 0;
if (!isExpanded) {
productsContainer.classList.add('collapsed');
category.div.classList.add('collapsed');
} else {
category.div.classList.add('expanded');
}
// Ajouter l'icône chevron
const icon = document.createElement('i');
icon.className = 'fa fa-chevron-down accordion-icon';
// Vérification de sécurité avant d'ajouter l'icône
if (category.h4 && category.h4.appendChild) {
category.h4.appendChild(icon);
}
// Ajouter le gestionnaire de clic
category.div.style.cursor = 'pointer';
category.div.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const isNowExpanded = !productsContainer.classList.contains('collapsed');
productsContainer.classList.toggle('collapsed', isNowExpanded);
category.div.classList.toggle('collapsed', isNowExpanded);
category.div.classList.toggle('expanded', !isNowExpanded);
console.log(`[Commerce Accordion] ${category.name}: ${isNowExpanded ? 'fermé' : 'ouvert'}`);
});
console.log(`[Commerce Accordion] ${category.name}: ${products.length} produits, état initial: ${isExpanded ? 'ouvert' : 'fermé'}`);
});
});
// ============================================================================
// TASK-2.4 : Section bâtiment collapsible
// ============================================================================
InitQueue.register('Building Collapse', function initBuildingCollapse() {
if (!isMobileMode()) {return;}
const batimentHeader = Array.from(document.querySelectorAll('h3.panel-title')).find(h =>
h.textContent.includes('Bâtiment')
);
if (!batimentHeader) {return;}
const panelHeading = batimentHeader.parentElement;
if (!panelHeading) {return;}
const panelBody = panelHeading.nextElementSibling;
if (!panelBody || !panelBody.classList.contains('panel-body')) {return;}
// Ajouter les classes
panelHeading.classList.add('building-section-header');
panelBody.classList.add('building-section-content');
// Ajouter l'icône chevron
const icon = document.createElement('i');
icon.className = 'fa fa-chevron-down accordion-icon';
panelHeading.appendChild(icon);
// État initial : ouvert
panelHeading.classList.add('expanded');
// Gestionnaire de clic
panelHeading.style.cursor = 'pointer';
panelHeading.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const isNowExpanded = !panelBody.classList.contains('collapsed');
panelBody.classList.toggle('collapsed', isNowExpanded);
panelHeading.classList.toggle('collapsed', isNowExpanded);
panelHeading.classList.toggle('expanded', !isNowExpanded);
console.log(`[Building Section] ${isNowExpanded ? 'fermé' : 'ouvert'}`);
});
console.log('[Building Section] Initialisé - section collapsible');
});
// ============================================================================
// TASK-2.1: CROIX DIRECTIONNELLE ET ACCÈS AUX PIÈCES EN LIGNE
// Afficher la croix (4 directions) et les accès aux pièces sur une ligne
// ============================================================================
(function () {
function initNavigationRow() {
if (!document.body.classList.contains('mobile-mode')) {return;}
// Utiliser un élément DOM préexistant comme ancre (pas .mobile-quick-actions créé par le script)
const navbar = document.querySelector('.navbar');
if (!navbar) {return;}
// Trouver l'image "Sortir" avec la croix directionnelle
const exitImg = document.querySelector('img[alt="Sortir"]');
if (!exitImg || !exitImg.parentElement) {return;}
const parent = exitImg.parentElement;
const map = parent.querySelector('map[name="exitmap"]');
// Trouver toutes les images bat*.gif (accès aux pièces)
const allBatImages = Array.from(document.querySelectorAll('img[src*="/bat/bat"]'));
if (allBatImages.length === 0) {return;}
// Trouver les images qui ne sont PAS déjà dans notre ligne créée
const originalImages = allBatImages.filter(img => {
let current = img;
while (current && current !== document.body) {
if (current.classList && current.classList.contains('kr-navigation-row')) {
return false;
}
current = current.parentElement;
}
return true;
});
// Trier les images par leur numéro (bat0, bat1, bat2, bat3, etc.)
originalImages.sort((a, b) => {
const numA = parseInt(a.src.match(/bat(\d+)\.gif/)?.[1] || '999');
const numB = parseInt(b.src.match(/bat(\d+)\.gif/)?.[1] || '999');
return numA - numB;
});
if (originalImages.length === 0) {return;}
// Trouver et masquer le conteneur d'origine (div.row.center)
const originalContainer = parent.closest('.row.center');
if (originalContainer) {
originalContainer.style.display = 'none';
}
// Récupérer les liens parents des images
const roomLinks = originalImages.map(img => img.closest('a')).filter(link => link !== null);
if (roomLinks.length === 0) {return;}
// Créer le conteneur de la ligne de navigation
const navRow = document.createElement('div');
navRow.className = 'kr-navigation-row';
navRow.setAttribute('role', 'group');
// Créer la croix directionnelle en premier
if (exitImg && map) {
const directionGroup = document.createElement('div');
directionGroup.className = 'btn-group kr-direction-cross';
directionGroup.setAttribute('role', 'group');
// Créer un lien style btn pour la croix
const directionLink = document.createElement('div');
directionLink.className = 'btn btn-default alert11 mini kr-direction-link';
// Cloner l'image et la map
const exitImgClone = exitImg.cloneNode(true);
exitImgClone.style.width = '60px';
exitImgClone.style.height = '60px';
exitImgClone.style.display = 'block';
const mapClone = map.cloneNode(true);
// En desktop, l'interaction se fait via les areas de la map
// En mobile, on doit intercepter les clics sur le conteneur et les rediriger vers les areas
directionLink.addEventListener('click', (e) => {
// Calculer les coordonnées relatives du clic
const rect = directionLink.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Trouver l'area qui correspond à ces coordonnées
const areas = mapClone.querySelectorAll('area');
for (const area of areas) {
if (area.shape === 'rect') {
const coords = area.coords.split(',').map(c => parseInt(c));
const [x1, y1, x2, y2] = coords;
if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
e.preventDefault();
e.stopPropagation();
const url = area.href;
if (url && url !== '#') {
window.location.href = url;
}
return;
}
}
}
});
directionLink.appendChild(exitImgClone);
directionLink.appendChild(mapClone);
directionGroup.appendChild(directionLink);
navRow.appendChild(directionGroup);
}
// Créer une carte pour chaque accès aux pièces
roomLinks.forEach(link => {
const btnGroup = document.createElement('div');
btnGroup.className = 'btn-group kr-room-access-card';
btnGroup.setAttribute('role', 'group');
// Cloner le lien pour ne pas modifier l'original
const linkClone = link.cloneNode(true);
// Utiliser les mêmes classes que les actions rapides
linkClone.className = 'btn btn-default alert11 mini kr-room-link';
// Forcer la navigation pour éviter l'interception par Kraland
linkClone.addEventListener('click', (e) => {
e.stopPropagation();
const url = linkClone.href;
if (url && url !== '#') {
window.location.href = url;
}
});
btnGroup.appendChild(linkClone);
navRow.appendChild(btnGroup);
});
// Insérer la nouvelle ligne après la navbar (élément préexistant)
// Note: On utilise .navbar (DOM original) et non .mobile-quick-actions (créé par le script)
navbar.parentNode.insertBefore(navRow, navbar.nextSibling);
console.log(`[Navigation Row] Initialisée avec croix directionnelle et ${roomLinks.length} accès aux pièces`);
}
// Enregistrer dans la queue d'initialisation mobile (priorité 60 - après quick actions)
InitQueue.register('initNavigationRow', initNavigationRow, 60);
})();
// ============================================================================
// FIX MOBILE : Empêcher le scroll automatique vers #flap ou autres ancres
// ============================================================================
if (window.innerWidth < 768 && window.location.hash && window.location.hash !== '#top') {
// Sauvegarder la position actuelle avant que le navigateur ne scrolle
const initialScrollRestoration = history.scrollRestoration;
history.scrollRestoration = 'manual';
// Supprimer l'ancre de l'URL
const cleanUrl = window.location.pathname + window.location.search;
history.replaceState(null, '', cleanUrl);
// Forcer le scroll en haut immédiatement et après le chargement
const forceScrollTop = () => window.scrollTo(0, 0);
forceScrollTop();
// S'assurer que le scroll reste en haut même après le chargement complet
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', forceScrollTop);
}
window.addEventListener('load', forceScrollTop);
// Restaurer le comportement par défaut après 500ms
setTimeout(() => {
history.scrollRestoration = initialScrollRestoration;
}, 500);
}
// ============================================================================
// NOUVELLES : Gestion du repliement/dépliement (mobile uniquement)
// ============================================================================
(function () {
function initNewsToggle() {
if (!document.body.classList.contains('mobile-mode')) {return;}
const newsToggle = document.getElementById('slide-submenu');
const newsContainer = document.getElementById('player-header-section');
if (newsToggle && newsContainer) {
// Fonction pour mettre à jour le bouton
function updateButton(isCollapsed) {
newsToggle.innerHTML = isCollapsed ? '▼' : '×';
newsToggle.setAttribute('aria-label', isCollapsed ? 'Déplier les nouvelles' : 'Replier les nouvelles');
newsToggle.setAttribute('title', isCollapsed ? 'Déplier' : 'Replier');
}
// Charger l'état depuis localStorage
const isCollapsed = localStorage.getItem('kr-news-collapsed') === 'true';
if (isCollapsed) {
newsContainer.classList.add('kr-news-collapsed');
}
updateButton(isCollapsed);
// Gérer le clic (capturer en premier pour empêcher Bootstrap)
newsToggle.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation(); // Empêche les autres handlers Bootstrap
newsContainer.classList.toggle('kr-news-collapsed');
// Sauvegarder l'état et mettre à jour le bouton
const collapsed = newsContainer.classList.contains('kr-news-collapsed');
localStorage.setItem('kr-news-collapsed', collapsed);
updateButton(collapsed);
}, { capture: true }); // Capture phase pour intercepter avant Bootstrap
console.log('[News Toggle] Initialisé');
}
}
// Enregistrer dans la queue d'initialisation mobile (priorité 80)
InitQueue.register('initNewsToggle', initNewsToggle, 80);
})();
// ============================================================================
// CONFIGURATION
// ============================================================================
const CONFIG = {
BUNDLED_CSS: `/* ============================================================================
1. CSS VARIABLES
============================================================================ */
:root {
--kr-primary: #8b0f0e;
--kr-primary-dark: #700b09;
--kr-gold: #C69100;
--kr-highlight: #c41e3a;
--kr-highlight-reverse: #f9d9de;
--kr-surface: #fff;
--kr-text: #0f1724;
--kr-muted: #6b7280;
--kr-navbar-bg: #111315;
--kr-radius: .5rem;
--kr-avatar-size: 120px;
/* Variables pour le dark mode */
--kr-bg-page: #f5f5f5;
--kr-bg-surface: #fff;
--kr-bg-elevated: #fff;
--kr-bg-hover: rgb(0,0,0,0.02);
--kr-bg-active: rgb(0,0,0,0.05);
--kr-text-primary: #0f1724;
--kr-text-secondary: #6b7280;
--kr-text-muted: #9ca3af;
--kr-text-inverse: #fff;
--kr-border-default: rgb(0,0,0,0.08);
--kr-border-strong: rgb(0,0,0,0.15);
--kr-shadow-sm: 0 1px 2px rgb(0,0,0,0.05);
--kr-shadow-md: 0 4px 6px rgb(0,0,0,0.07);
--kr-shadow-lg: 0 10px 15px rgb(0,0,0,0.1);
/* Couleurs spécifiques */
--kr-white: #fff;
--kr-focus-ring: rgb(164,18,13,0.22);
--kr-focus-ring-light: rgb(164,18,13,0.18);
--kr-alert-info-bg: rgb(164,18,13,0.06);
--kr-alert-info-border: rgb(164,18,13,0.14);
--kr-btn-shadow: rgb(165,18,13,0.12);
--kr-badge-danger: #dc3545;
--kr-badge-danger-border: #bd2130;
--kr-badge-pnj: #d9534f;
--kr-bootstrap-blue: #337ab7;
/* Couleurs nations (.f1-.f9) */
--kr-nation-1: #C41E3A;
--kr-nation-2: #C69100;
--kr-nation-3: #FF0;
--kr-nation-4: #0033A0;
--kr-nation-5: #0B6623;
--kr-nation-6: #6A0DAD;
--kr-nation-7: #6B7280;
--kr-nation-8: #0A6B2D;
--kr-nation-9: #a70;
/* Couleurs nations fonds (.c1-.c12) - reproduit depuis la feuille de style principale */
--kr-nation-bg-1: rgb(255, 128, 128);
--kr-nation-bg-2: rgb(170, 112, 0);
--kr-nation-bg-3: rgb(255, 255, 128);
--kr-nation-bg-4: rgb(128, 128, 255);
--kr-nation-bg-5: rgb(128, 255, 128);
--kr-nation-bg-6: rgb(204, 128, 255);
--kr-nation-bg-7: rgb(170, 170, 170);
--kr-nation-bg-8: rgb(115, 151, 115);
--kr-nation-bg-9: rgb(170, 170, 68);
--kr-nation-bg-10: rgb(204, 255, 255);
/* CLASSES KRALAND - Fonds nations (.c1-.c12) */
.c1 { background-color: var(--kr-nation-bg-1) !important; }
.c2 { background-color: var(--kr-nation-bg-2) !important; }
.c3 { background-color: var(--kr-nation-bg-3) !important; }
.c4 { background-color: var(--kr-nation-bg-4) !important; }
.c5 { background-color: var(--kr-nation-bg-5) !important; }
.c6 { background-color: var(--kr-nation-bg-6) !important; }
.c7 { background-color: var(--kr-nation-bg-7) !important; }
.c8 { background-color: var(--kr-nation-bg-8) !important; }
.c9 { background-color: var(--kr-nation-bg-9) !important; }
.c10 { background-color: var(--kr-nation-bg-10) !important; }
/* Forcer texte noir sur les labels nationaux (lisibilité) */
.mini.c1, .mini.c2, .mini.c3, .mini.c4, .mini.c5, .mini.c6, .mini.c7, .mini.c8, .mini.c9, .mini.c10 {
color: #000 !important;
text-shadow: none !important;
}
/* ============================================
MOBILE ADAPTATION - VARIABLES (TASK-1.1)
============================================ */
/* Hauteurs fixes mobile */
--mobile-header-height: 56px;
--mobile-tab-bar-height: 48px;
--mobile-touch-target: 44px;
/* Espacements */
--mobile-spacing-xs: 4px;
--mobile-spacing-sm: 8px;
--mobile-spacing-md: 12px;
--mobile-spacing-lg: 16px;
--mobile-spacing-xl: 24px;
/* Bordures */
--mobile-radius: 8px;
--mobile-radius-lg: 16px;
/* Z-index */
--z-header: 1000;
--z-tab-bar: 999;
--z-bottom-sheet: 998;
--z-drawer: 1001;
/* Transitions */
--transition-fast: 0.15s ease;
--transition-normal: 0.3s ease;
--transition-slow: 0.5s ease;
/* Couleurs des jauges (Bootstrap success/info/warning) */
--kr-gauge-pv: #28a745; /* Vert Bootstrap success */
--kr-gauge-pm: #007bff; /* Bleu Bootstrap info */
--kr-gauge-pp: #ffc107; /* Jaune Bootstrap warning */
/* Overlays et transparence pour navbar sombre */
--kr-overlay-light-10: rgba(255, 255, 255, 0.1);
--kr-overlay-light-05: rgba(255, 255, 255, 0.05);
--kr-overlay-light-20: rgba(255, 255, 255, 0.2);
--kr-overlay-dark-20: rgba(0, 0, 0, 0.2);
--kr-overlay-dark-30: rgba(0, 0, 0, 0.3);
--kr-overlay-dark-125: rgba(0, 0, 0, 0.125);
}
html.kr-theme-variant-empire-brun {
--kr-primary: #5E3B2D;
--kr-highlight: #C69100;
}
html.kr-theme-variant-paladium {
--kr-primary: #D4AF37;
--kr-highlight: #044c17;
}
html.kr-theme-variant-theocratie-seelienne {
--kr-primary: #0033A0;
--kr-highlight: #2d5fcb;
}
html.kr-theme-variant-paradigme-vert {
--kr-primary: #0B6623;
--kr-highlight: #063803;
}
html.kr-theme-variant-khanat-elmerien {
--kr-primary: #6A0DAD;
--kr-highlight: #7b4c9c;
}
html.kr-theme-variant-confederation-libre {
--kr-primary: #6B7280;
--kr-highlight: #475369;
}
html.kr-theme-variant-royaume-ruthvenie {
--kr-primary: #0A6B2D;
--kr-highlight: #C41E3A;
}
html.kr-theme-variant-empire-brun-dark {
--kr-primary: #7a5240;
--kr-primary-dark: #5E3B2D;
--kr-highlight: #d4a574;
--kr-highlight-reverse: #1a0f0a;
/* Dark mode surfaces */
--kr-bg-page: #12100d;
--kr-bg-surface: #1e1a15;
--kr-bg-elevated: #2a251f;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #f5f1ed;
--kr-text-secondary: #b8a997;
--kr-text-muted: #8a7968;
--kr-text-inverse: #12100d;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #3a332b;
--kr-form-bg-focus: #423a31;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #a29bfe;
--kr-nation-7: #b2bec3;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-paladium-dark {
--kr-primary: #e6c76e;
--kr-primary-dark: #D4AF37;
--kr-highlight: #4a9d5f;
--kr-highlight-reverse: #021a0a;
/* Dark mode surfaces */
--kr-bg-page: #0f0e0a;
--kr-bg-surface: #1a1810;
--kr-bg-elevated: #252015;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #f5f3e8;
--kr-text-secondary: #c4b896;
--kr-text-muted: #938567;
--kr-text-inverse: #0f0e0a;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #3a3420;
--kr-form-bg-focus: #43392a;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #e6c76e;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #a29bfe;
--kr-nation-7: #b2bec3;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-theocratie-seelienne-dark {
--kr-primary: #5a8fd9;
--kr-primary-dark: #2d5fcb;
--kr-highlight: #80b3ff;
--kr-highlight-reverse: #000d1f;
/* Dark mode surfaces */
--kr-bg-page: #0a0e15;
--kr-bg-surface: #12182a;
--kr-bg-elevated: #1a2438;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #e8f0ff;
--kr-text-secondary: #9db5d6;
--kr-text-muted: #6b7f9e;
--kr-text-inverse: #0a0e15;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #2a3447;
--kr-form-bg-focus: #323d54;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #80b3ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #a29bfe;
--kr-nation-7: #b2bec3;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-paradigme-vert-dark {
--kr-primary: #4d9c61;
--kr-primary-dark: #2a7a3d;
--kr-highlight: #70c784;
--kr-highlight-reverse: #021508;
/* Dark mode surfaces */
--kr-bg-page: #0a120d;
--kr-bg-surface: #0f1a13;
--kr-bg-elevated: #1a2820;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #e8f5ed;
--kr-text-secondary: #a0c4ab;
--kr-text-muted: #6d8a75;
--kr-text-inverse: #0a120d;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #2a3d30;
--kr-form-bg-focus: #324539;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #70c784;
--kr-nation-6: #a29bfe;
--kr-nation-7: #b2bec3;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-khanat-elmerien-dark {
--kr-primary: #96c;
--kr-primary-dark: #7b4c9c;
--kr-highlight: #b794d9;
--kr-highlight-reverse: #1a0425;
/* Dark mode surfaces */
--kr-bg-page: #0f0a15;
--kr-bg-surface: #1a121f;
--kr-bg-elevated: #251a2d;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #f0e8ff;
--kr-text-secondary: #bba0d6;
--kr-text-muted: #857299;
--kr-text-inverse: #0f0a15;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #3a2f47;
--kr-form-bg-focus: #443854;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #b794d9;
--kr-nation-7: #b2bec3;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-confederation-libre-dark {
--kr-primary: #9ca3af;
--kr-primary-dark: #6b7280;
--kr-highlight: #c2c9d6;
--kr-highlight-reverse: #0f1115;
/* Dark mode surfaces */
--kr-bg-page: #0e1013;
--kr-bg-surface: #16181c;
--kr-bg-elevated: #1f2228;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #e8eaed;
--kr-text-secondary: #b8bcc4;
--kr-text-muted: #7e8591;
--kr-text-inverse: #0e1013;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #2d3139;
--kr-form-bg-focus: #363a43;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #a29bfe;
--kr-nation-7: #c2c9d6;
--kr-nation-8: #81c784;
--kr-nation-9: #c4b958;
}
html.kr-theme-variant-royaume-ruthvenie-dark {
--kr-primary: #4a9d61;
--kr-primary-dark: #2a7a3d;
--kr-highlight: #ff6b85;
--kr-highlight-reverse: #1a0408;
/* Dark mode surfaces */
--kr-bg-page: #0d1210;
--kr-bg-surface: #141a16;
--kr-bg-elevated: #1d2621;
--kr-bg-hover: rgba(255, 255, 255, 0.03);
--kr-bg-active: rgba(255, 255, 255, 0.06);
/* Color scheme */
color-scheme: dark;
/* Dark mode texte */
--kr-text-primary: #ecf5f0;
--kr-text-secondary: #a5c4b0;
--kr-text-muted: #708a79;
--kr-text-inverse: #0d1210;
/* Dark mode bordures */
--kr-border-default: rgb(255,255,255,0.08);
--kr-border-strong: rgb(255,255,255,0.15);
/* Dark mode ombres */
--kr-shadow-sm: 0 1px 3px rgb(0,0,0,0.4);
--kr-shadow-md: 0 4px 8px rgb(0,0,0,0.5);
--kr-shadow-lg: 0 10px 20px rgb(0,0,0,0.6);
/* Dark mode form controls */
--kr-form-bg: #2d3d33;
--kr-form-bg-focus: #36463b;
/* Couleurs nations mode sombre (.f1-.f9) */
--kr-nation-1: #ff8080;
--kr-nation-2: #d4a574;
--kr-nation-3: #ffe066;
--kr-nation-4: #74b9ff;
--kr-nation-5: #55efc4;
--kr-nation-6: #a29bfe;
--kr-nation-7: #b2bec3;
--kr-nation-8: #70c784;
--kr-nation-9: #c4b958;
}
/* ============================================================================
2. LAYOUT OVERRIDES
============================================================================ */
/* Hide top header with Kraland logo */
#top {
display: none !important;
}
html {
height: auto !important; /* Force HTML à grandir avec le contenu */
}
body {
min-height: 100vh !important; /* Le body fait au moins la hauteur du viewport pour les pages courtes */
height: auto !important; /* Laisser le body grandir avec le contenu */
display: flex;
flex-direction: column;
margin: 0 !important; /* Pas de margin pour éviter le décalage de 60px */
padding-top: 60px; /* Compenser la navigation fixe (60px) */
}
/* La navigation en haut est en position fixed, donc elle sort du flux */
nav.navbar {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 1030 !important; /* Au-dessus du contenu */
height: auto !important; /* Laisser la hauteur s'adapter au contenu */
max-height: 60px !important; /* Limiter la hauteur maximale */
}
/* Le contenu principal occupe l'espace disponible */
#content {
flex: 1 0 auto;
}
footer {
/* Footer en position normale, collé en bas grâce au flexbox */
flex-shrink: 0;
width: 100%;
}
/* Option pour masquer la citation du footer */
html.kr-hide-footer-quote .footer-quote {
display: none !important;
}
/* Increase container width: remove 150px from each side */
.container {
max-width: 1608px !important;
width: 1608px !important;
}
/* Show skills panel (no longer collapsed by default) */
#skills-panel {
display: block !important;
border: 1px solid rgb(0, 0, 0, 0.06) !important;
border-radius: 5px !important;
background-color: var(--kr-white) !important;
}
/* ============================================================================
3. NAVIGATION
============================================================================ */
/* Navbar brand (logo) alignment */
.navbar-brand {
display: flex !important;
align-items: center !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.navbar-brand img.kr-logo {
height: 28px;
vertical-align: middle;
}
.navbar-nav > li > a{
color: var(--kr-white) !important;
}
/* ============================================================================
4. FORMS
============================================================================ */
.form-control.form-control {
border-radius: .4rem;
border: 1px solid rgb(0,0,0,0.08);
padding: .6rem .8rem;
font-size: .95rem;
}
.form-control:focus {
box-shadow: 0 0 0 .18rem var(--kr-focus-ring);
border-color: var(--kr-primary);
outline: none;
}
textarea.form-control.form-control {
font-size: 1.4rem !important;
line-height: 1.6 !important;
}
.form-group label {
color: var(--kr-text);
font-weight: 600;
}
input[type="checkbox"],
input[type="radio"] {
accent-color: var(--kr-primary) !important;
}
input[type="checkbox"]:focus,
input[type="radio"]:focus {
outline: none;
box-shadow: 0 0 0 .12rem var(--kr-focus-ring-light) !important;
}
input[type="checkbox"]:checked {
border-color: var(--kr-primary) !important;
}
/* ============================================================================
5. BADGES, LABELS & ALERTS
Consolidated color remapping for theme
============================================================================ */
/* Badges & labels - info/primary variants use red */
.label,
.badge {
background-color: var(--kr-primary);
color: var(--kr-white);
border-color: var(--kr-primary-dark);
}
.bg-info {
background-color: var(--kr-primary);
color: var(--kr-white);
}
/* Badge danger - notification alert always red on all themes */
.badge-danger {
background-color: var(--kr-badge-danger) !important;
color: var(--kr-white) !important;
border-color: var(--kr-badge-danger-border) !important;
}
/* Alerts - info variant */
.alert.alert-info {
background-color: var(--kr-alert-info-bg);
border-color: var(--kr-alert-info-border);
color: var(--kr-text);
}
.alert strong,
.alert p {
color: var(--kr-text);
}
/* ============================================================================
6. LIST GROUPS
============================================================================ */
.list-group-item {
border: none;
padding: .6rem .75rem;
}
.list-group-item.btn {
border: none !important;
}
.list-group-item.active,
.list-group-item.active:focus,
.list-group-item.active:hover,
.list-group-item.active a {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary-dark) !important;
color: var(--kr-surface) !important;
}
/* Keep hover for non-active items subtle and themed */
.list-group-item:hover {
color: var(--kr-primary-dark);
background-color: rgb(0,0,0,0.02);
}
/* ============================================================================
7. AVATARS
Ensure avatars are displayed at least the configured size and crop nicely
============================================================================ */
.img-circle {
width: var(--kr-avatar-size);
height: var(--kr-avatar-size);
min-width: var(--kr-avatar-size);
min-height: var(--kr-avatar-size);
object-fit: cover;
display: inline-block;
border-radius: 50%;
}
/* Fix: Override Bootstrap .img-responsive on avatars to prevent stretching */
.img-circle.img-responsive,
.img-circle.img-thumbnail {
max-width: none !important;
width: var(--kr-avatar-size);
height: var(--kr-avatar-size);
}
/* Ensure layout doesn't break: allow the author column to accommodate larger avatars */
.col-md-3.sidebar .avatar,
.col-md-2.sidebar .avatar {
margin-bottom: 0.5rem;
}
/* Align list-group user items with their avatar to prevent overflow */
.list-group a.list-group-item.ds_user,
.list-group-item.ds_user {
position: relative;
min-height: var(--kr-avatar-size) !important;
padding-right: calc(var(--kr-avatar-size) + 1rem) !important;
display: flex !important;
align-items: center !important;
}
.list-group-item.ds_user .pull-right {
position: absolute !important;
right: .75rem !important;
top: 50% !important;
transform: translateY(-50%) !important;
}
/* ============================================================================
7.1 PLAYER HEADER SECTION - FIX BOOTSTRAP GRID
Le HTML utilise des classes .nopadding qui cassent le système de grille Bootstrap.
On restaure ici les paddings standards pour que les .row et .col- fonctionnent correctement.
============================================================================ */
/* Restaurer le padding Bootstrap standard (15px) pour toutes les colonnes dans le header */
#player-header-section .nopadding,
#player-header-section .nopadding-right,
#player-main-panel .nopadding,
#player-main-panel .nopadding-right,
#player-vitals-section .nopadding,
#player-vitals-section .nopadding-right {
padding-left: 15px !important;
padding-right: 15px !important;
}
/* Exception : le bouton avatar peut avoir moins de padding pour rester centré */
#player-main-panel > .row > .nopadding:first-child {
padding-left: 10px !important;
padding-right: 10px !important;
}
/* Corriger le padding-left inline sur player-vitals-section */
#player-vitals-section[style*="padding-left"] {
padding-left: 0 !important;
}
/* Structurer correctement player-actions-section qui contient des boutons sans colonnes */
#player-actions-section {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0 15px !important;
}
/* Espacement entre les lignes de boutons d'accès rapide */
.kr-quick-access-buttons .col-xs-6 {
margin-bottom: 10px;
}
#player-actions-section hr {
width: 100%;
margin: 0.5rem 0;
}
/* ============================================================================
8. CAROUSEL
============================================================================ */
.carousel-caption {
background: linear-gradient(180deg, rgb(0,0,0,0.55), rgb(0,0,0,0.35));
padding: 1.4rem;
padding-bottom: 56px;
border-radius: .6rem;
color: var(--kr-white);
}
.item img[class*="-slide"] {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
}
a.carousel-control.left,
a.carousel-control.right{
background-image: none;
}
/* ============================================================================
9. MODALS
Fix modal overflow - prevent modals from extending beyond viewport
Allow clicking outside to close
============================================================================ */
/* Conteneur modal - empêcher le scroll du conteneur */
.modal {
display: flex !important;
position: fixed !important; /* Nécessaire pour que la modal reste par-dessus le contenu */
align-items: flex-start; /* Évite le scroll automatique */
justify-content: center;
overflow-y: hidden !important; /* Pas de scroll sur le conteneur modal */
pointer-events: auto;
padding-top: 30px; /* Espacement du haut */
}
.modal.in {
/* Force le scroll à rester en haut lors de l'ouverture */
overflow-y: hidden !important;
}
/* Dialog avec contraintes de hauteur et son propre scroll */
.modal-dialog {
width: 900px;
max-width: 90vw;
margin: 0 auto 30px; /* Pas de margin-top car géré par padding du parent */
max-height: calc(100vh - 60px);
display: flex;
flex-direction: column;
pointer-events: auto;
overflow-y: auto; /* Le dialog peut scroller si nécessaire */
}
/* Content avec structure flex */
.modal-content {
display: flex;
flex-direction: column;
max-height: 100%;
overflow: hidden;
}
/* Body scrollable - le contenu peut scroller si trop grand */
.modal-body {
overflow-y: auto;
flex: 1 1 auto;
}
/* Header et footer restent fixes et toujours visibles */
.modal-header,
.modal-footer {
flex-shrink: 0;
}
/* ============================================================================
9b. BOOTBOX ORDER MODAL - MOBILE UX OPTIMIZATION
Améliore l'utilisabilité mobile de la modale d'ordre
- Zones tactiles 44px minimum (WCAG 2.1)
- Grille responsive pour les actions
- Footer sticky
- Prévention zoom iOS (font-size 16px+)
⚠️ MOBILE ONLY - Ne s'applique QUE sur mobile (<768px)
============================================================================ */
@media (width <= 767px) {
/* 1. SELECT PERSONNAGE */
.bootbox-confirm .modal-body > select:first-of-type,
.bootbox-confirm select.form-control {
min-height: var(--mobile-touch-target);
font-size: 16px !important; /* Évite zoom iOS */
padding: 8px 12px;
margin-bottom: var(--mobile-spacing-lg);
border-radius: var(--mobile-radius);
}
/* 2. ACTIONS PRIMAIRES/SECONDAIRES EN GRILLE */
.bootbox-confirm .panel-heading ul.nav-tabs {
display: grid !important; /* Override Bootstrap flex */
grid-template-columns: repeat(2, 1fr); /* 2 colonnes sur mobile */
gap: var(--mobile-spacing-md) !important;
padding-left: 0;
margin-bottom: var(--mobile-spacing-md);
border-bottom: none !important; /* Retire la bordure des nav-tabs Bootstrap */
}
.bootbox-confirm .panel-heading ul.nav-tabs > li {
margin: 0 !important;
padding: 0;
display: block;
float: none !important; /* Override Bootstrap float */
}
.bootbox-confirm .panel-heading ul.nav-tabs > li > a {
display: flex;
align-items: center;
justify-content: center;
min-height: var(--mobile-touch-target);
padding: 12px 16px;
text-align: center;
background: var(--kr-bg-elevated);
border: 1px solid var(--kr-border-default) !important;
border-radius: var(--mobile-radius) !important;
font-weight: 500;
font-size: 0.95rem;
text-decoration: none;
transition: all var(--transition-fast);
margin: 0 !important; /* Override Bootstrap margin */
}
.bootbox-confirm .panel-heading ul.nav-tabs > li.active > a {
background: var(--kr-primary);
color: white;
border-color: var(--kr-primary) !important;
}
.bootbox-confirm .panel-heading ul.nav-tabs > li > a:hover,
.bootbox-confirm .panel-heading ul.nav-tabs > li > a:focus {
background: var(--kr-bg-hover);
text-decoration: none;
transform: translateY(-1px);
box-shadow: var(--kr-shadow-sm);
border-color: var(--kr-primary) !important;
}
.bootbox-confirm .panel-heading ul.nav-tabs > li > a:active {
transform: translateY(0);
background: var(--kr-bg-active);
}
/* Icônes dans les actions (si présentes) */
.bootbox-confirm .list-inline > li > a > .fa,
.bootbox-confirm .list-inline > li > a > .glyphicon {
margin-right: 6px;
font-size: 1.1em;
}
/* 3. TABLEAU RADIO BUTTONS */
/* 3. PANEL ACTIONS - Layout Grid Compact */
.bootbox-confirm .panel-actions {
padding: var(--mobile-spacing-md);
background: var(--kr-bg-surface);
border-radius: var(--mobile-radius);
}
/* Headers "Actions / Diff. / Jet" */
.bootbox-confirm .panel-actions::before {
content: "Actions";
display: block;
font-weight: 600;
font-size: 0.8125rem;
color: var(--kr-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--mobile-spacing-sm);
padding-bottom: var(--mobile-spacing-sm);
border-bottom: 1px solid var(--kr-border-default);
}
/* ACTIONS GRID - Layout compact à 4 colonnes */
.bootbox-confirm .panel-actions .row.form-group {
display: grid !important;
grid-template-columns: 44px 1fr 60px 70px;
gap: 0;
padding: 0 !important;
margin: 0 0 2px !important;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 4px;
overflow: hidden;
transition: all 0.2s ease;
}
/* Hover sur row */
.bootbox-confirm .panel-actions .row.form-group:hover {
background: rgba(212, 165, 116, 0.05);
border-color: rgba(212, 165, 116, 0.3);
transform: translateX(2px);
}
/* Row checked - highlight fort */
.bootbox-confirm .panel-actions .row.form-group:has(input[type="radio"]:checked) {
background: rgba(212, 165, 116, 0.12);
border-color: var(--kr-primary);
box-shadow: 0 0 0 1px rgba(212, 165, 116, 0.2);
}
/* Cellules du grid */
.bootbox-confirm .panel-actions .row.form-group > div {
padding: 8px 6px !important;
margin: 0 !important;
display: flex !important;
align-items: center;
min-height: 44px;
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
.bootbox-confirm .panel-actions .row.form-group > div:last-child {
border-right: none;
}
/* Colonne 1 : Radio button (centré) */
.bootbox-confirm .panel-actions .row.form-group > div:nth-child(1) {
justify-content: center;
background: rgba(0, 0, 0, 0.1);
}
/* Colonne 2 : Label (aligné à gauche) */
.bootbox-confirm .panel-actions .row.form-group > div:nth-child(2) {
padding-left: 12px !important;
}
/* Colonnes 3 & 4 : Diff et Jet (alignés à droite, font compact) */
.bootbox-confirm .panel-actions .row.form-group > div:nth-child(3),
.bootbox-confirm .panel-actions .row.form-group > div:nth-child(4) {
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
color: var(--kr-text-secondary);
background: rgba(0, 0, 0, 0.15);
}
/* Radio buttons - Style custom */
.bootbox-confirm .panel-actions input[type="radio"] {
width: 20px !important;
height: 20px !important;
min-width: 20px !important;
min-height: 20px !important;
cursor: pointer;
accent-color: var(--kr-primary);
margin: 0 !important;
flex-shrink: 0;
}
/* Labels - Style clickable */
.bootbox-confirm .panel-actions label {
cursor: pointer;
margin: 0 !important;
font-size: 0.9375rem;
font-weight: 500;
color: var(--kr-text-primary);
transition: color 0.2s ease;
user-select: none;
}
.bootbox-confirm .panel-actions label:hover {
color: var(--kr-primary);
}
/* TABLE OBJETS - Style normal conservé */
.bootbox-confirm table {
width: 100%;
margin-bottom: var(--mobile-spacing-lg);
border-collapse: separate;
border-spacing: 0;
}
.bootbox-confirm table td,
.bootbox-confirm table th {
padding: 12px 8px;
vertical-align: middle;
border-bottom: 1px solid var(--kr-border-default);
}
.bootbox-confirm table th {
font-weight: 600;
font-size: 0.875rem;
color: var(--kr-text-secondary);
background: var(--kr-bg-elevated);
}
/* 4. CHECKBOXES & BONUS - Ligne horizontale unifiée */
.bootbox-confirm input[type="checkbox"] {
width: 20px;
height: 20px;
min-width: 20px;
min-height: 20px;
cursor: pointer;
accent-color: var(--kr-primary);
margin: 0;
}
/* Groupes de formulaire en ligne */
.bootbox-confirm .form-group:has(input[type="checkbox"]),
.bootbox-confirm .form-group:has(select[name="bonus"]) {
display: inline-flex;
align-items: center;
gap: 12px;
min-height: 44px;
width: auto;
margin-right: var(--mobile-spacing-md);
margin-bottom: var(--mobile-spacing-md);
}
.bootbox-confirm .modal-body label {
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 44px;
margin-bottom: 0;
padding: 10px 0;
}
/* Select Bonus optimisé */
.bootbox-confirm select[name="bonus"] {
min-height: 44px;
padding: 10px 12px;
font-size: 0.9375rem;
border: 1px solid var(--kr-border-default);
border-radius: 8px;
background: var(--kr-bg-surface);
color: var(--kr-text-primary);
cursor: pointer;
}
.bootbox-confirm .form-group {
margin-bottom: var(--mobile-spacing-lg);
}
/* 5. TOOLBAR BBCODE */
.bootbox-confirm .btn-toolbar {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: var(--mobile-spacing-md);
}
.bootbox-confirm .btn-toolbar .btn-group {
margin-right: 0;
margin-bottom: 4px;
}
.bootbox-confirm .btn-toolbar .btn {
min-height: var(--mobile-touch-target);
min-width: var(--mobile-touch-target);
padding: 8px 12px;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.bootbox-confirm .btn-toolbar .btn:hover {
background: var(--kr-bg-hover);
border-color: var(--kr-primary);
}
/* 6. TEXTAREA MESSAGE */
.bootbox-confirm textarea.form-control {
min-height: 120px;
font-size: 16px !important; /* Évite zoom iOS */
line-height: 1.5;
resize: vertical;
}
/* ============================================================================
9c. MODAL ORDRE - OPTIMISATION MOBILE (MOBILE ONLY)
Structure sticky, zones identifiées, grille d'actions (nav-tabs)
============================================================================ */
/* GARDE-FOU : Tous les styles dans media query mobile uniquement */
@media (width <= 768px) {
/* === STRUCTURE FLEXBOX STICKY === */
/* IMPORTANT: Cibler uniquement .modal-content, pas .modal-dialog */
.bootbox-confirm > .modal-dialog {
display: block !important; /* Annuler tout flex sur modal-dialog */
}
.bootbox-confirm .modal-content {
display: flex !important;
flex-direction: column !important;
max-height: 90vh !important;
}
/* Header zone (select + title) - sticky */
.bootbox-confirm .kraland-modal-header,
.bootbox-confirm .kraland-character-title {
position: sticky !important;
top: 0 !important;
z-index: 110 !important;
background: white !important;
flex-shrink: 0 !important;
}
.bootbox-confirm .kraland-modal-header {
padding: 12px 16px 8px !important;
border-bottom: 1px solid #e0e0e0 !important;
}
.bootbox-confirm .kraland-character-title {
padding: 8px 16px !important;
margin: 0 !important;
font-size: 18px !important;
}
/* Actions zone (panel-heading avec nav-tabs) - sticky */
.bootbox-confirm .kraland-actions-zone {
position: sticky !important;
top: 0 !important;
z-index: 100 !important;
background: white !important;
padding: 12px 16px !important;
border-bottom: 2px solid #f0f0f0 !important;
}
/* Body scrollable */
.bootbox-confirm .modal-body {
flex: 1 !important;
overflow: hidden auto !important;
padding: 0 !important;
}
/* Zone de formulaire (panel-body) - scrollable */
.bootbox-confirm .kraland-form-zone {
padding: 16px !important;
}
/* Footer du panel (coût/durée) - sticky avant footer modal */
.bootbox-confirm .kraland-action-footer {
position: sticky !important;
bottom: 72px !important; /* Hauteur du footer modal */
z-index: 90 !important;
background: #f8f8f8 !important;
padding: 12px 16px !important;
border-top: 1px solid #e0e0e0 !important;
}
.bootbox-confirm .kraland-action-footer p {
margin: 0 !important;
font-size: 13px !important;
color: #666 !important;
}
/* Footer modal sticky (boutons OK/Cancel) */
.bootbox-confirm .kraland-modal-footer {
position: sticky !important;
bottom: 0 !important;
z-index: 110 !important;
background: white !important;
flex-shrink: 0 !important;
padding: 12px 16px !important;
border-top: 2px solid #e0e0e0 !important;
display: flex !important;
gap: 12px !important;
}
.bootbox-confirm .kraland-modal-footer .btn {
flex: 1 !important;
min-height: 56px !important;
font-size: 16px !important;
border-radius: 8px !important;
}
/* === OPTIMISATION DES NAV-TABS (déjà en grid par forceOrderModalGridLayout) === */
/* Les nav-tabs sont déjà stylés en grid 2 colonnes par le JS */
/* On améliore juste le spacing et les couleurs */
.bootbox-confirm .nav.nav-tabs li a {
font-size: 15px !important;
font-weight: 500 !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
transition: all 0.2s ease !important;
}
/* Hover sur les nav-tabs */
.bootbox-confirm .nav.nav-tabs li a:hover {
background: #f0f8ff !important;
text-decoration: none !important;
}
/* État actif des nav-tabs */
.bootbox-confirm .nav.nav-tabs li.active a {
background: #428bca !important;
border-color: #428bca !important;
color: white !important;
}
/* === OPTIMISATIONS FORMULAIRE === */
/* IMPORTANT: Annuler les flex indésirables sur certains éléments Bootstrap SAUF les panels avec tableau */
.bootbox-confirm .row,
.bootbox-confirm [class*="col-"],
.bootbox-confirm .form-group,
.bootbox-confirm .tab-content {
display: block !important;
}
/* EXCEPTION CRITIQUE: Les panels de type info (tableaux Actions) doivent utiliser flexbox */
.bootbox-confirm .panel-info .panel-heading,
.bootbox-confirm .panel-info .panel-body,
.bootbox-confirm .panel-info .panel-footer {
display: block !important; /* Garder block pour le container */
}
/* Les ROWS à l'intérieur des panels doivent être en flex pour l'alignement horizontal */
.bootbox-confirm .panel-info .panel-heading .row,
.bootbox-confirm .panel-info .panel-body .row,
.bootbox-confirm .panel-info .panel-footer .row {
display: flex !important;
flex-flow: row nowrap !important;
align-items: center !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
/* Les colonnes dans ces rows doivent avoir leur largeur Bootstrap */
.bootbox-confirm .panel-info .row > [class*="col-"] {
flex-shrink: 0 !important;
}
/* Largeurs spécifiques: Nouvelle répartition sm-0 / sm-7 / sm-3 / sm-3 */
/* IMPORTANT: Définir les largeurs AVANT les paddings pour éviter les écrasements */
.bootbox-confirm .panel-info .panel-heading .row > .col-sm-1,
.bootbox-confirm .panel-info .panel-body .row > .col-sm-1 {
flex: 0 0 0% !important;
max-width: 0% !important;
padding: 0 !important; /* Masquer complètement */
overflow: hidden !important;
width: 0 !important;
}
.bootbox-confirm .panel-info .panel-heading .row > .col-sm-7,
.bootbox-confirm .panel-info .panel-body .row > .col-sm-7 {
flex: 0 0 46% !important; /* 46% pour Actions */
max-width: 46% !important;
}
/* Premier col-sm-2 (Diff.) devient col-sm-3 visuellement */
.bootbox-confirm .panel-info .panel-heading .row > .col-sm-2:nth-child(3),
.bootbox-confirm .panel-info .panel-body .row > .col-sm-2:nth-child(3) {
flex: 0 0 27% !important; /* 27% pour Diff. */
max-width: 27% !important;
}
/* Deuxième col-sm-2 (Jet) devient col-sm-3 visuellement */
.bootbox-confirm .panel-info .panel-heading .row > .col-sm-2:nth-child(4),
.bootbox-confirm .panel-info .panel-body .row > .col-sm-2:nth-child(4) {
flex: 0 0 27% !important; /* 27% pour Jet */
max-width: 27% !important;
}
/* ESPACEMENT: Padding pour éviter que les colonnes soient collées (APRÈS les largeurs) */
.bootbox-confirm .panel-info .panel-heading .row > [class*="col-"]:not(.col-sm-1),
.bootbox-confirm .panel-info .panel-body .row > [class*="col-"]:not(.col-sm-1) {
padding-left: 8px !important;
padding-right: 8px !important;
}
/* === FOOTER: Layout spécifique pour Maladresse + Bonus === */
/* Footer: Groupement visuel avec widths fixes (proportions: 15% + 25% | 20% + 40%) */
/* Groupe 1: Checkbox (15%) + Maladresse (25%) = 40% */
.bootbox-confirm .panel-info .panel-footer .row > .col-sm-1 {
flex: 0 0 15% !important;
max-width: 15% !important;
padding-left: 8px !important;
padding-right: 4px !important;
}
.bootbox-confirm .panel-info .panel-footer .row > .col-sm-7 {
flex: 0 0 25% !important;
max-width: 25% !important;
padding-left: 0 !important;
padding-right: 12px !important; /* Gap avant groupe 2 */
}
/* Groupe 2: Bonus (20%) + Select (40%) = 60% */
.bootbox-confirm .panel-info .panel-footer .row > .col-sm-2:nth-child(3) {
flex: 0 0 20% !important;
max-width: 20% !important;
padding-left: 12px !important; /* Gap après groupe 1 */
padding-right: 4px !important;
}
.bootbox-confirm .panel-info .panel-footer .row > .col-sm-2:nth-child(4) {
flex: 0 0 40% !important;
max-width: 40% !important;
padding-left: 0 !important;
padding-right: 8px !important;
}
/* === Alignement vertical des éléments du footer === */
.bootbox-confirm .panel-info .panel-footer .row {
align-items: center !important;
}
.bootbox-confirm .panel-info .panel-footer input[type="checkbox"],
.bootbox-confirm .panel-info .panel-footer select,
.bootbox-confirm .panel-info .panel-footer label {
vertical-align: middle !important;
}
/* IMPORTANT: Préserver le comportement normal des tableaux Bootstrap */
.bootbox-confirm table {
display: table !important;
width: 100% !important;
table-layout: auto !important;
}
.bootbox-confirm table tbody {
display: table-row-group !important;
}
.bootbox-confirm table tr {
display: table-row !important;
}
.bootbox-confirm table td,
.bootbox-confirm table th {
display: table-cell !important;
vertical-align: middle !important;
}
/* Select personnage */
.bootbox-confirm .kraland-modal-header select {
width: 100% !important;
height: 48px !important;
font-size: 16px !important;
border-radius: 8px !important;
}
/* Image personnage dans title */
.bootbox-confirm .kraland-character-title img {
width: 60px !important;
height: 60px !important;
border-radius: 8px !important;
object-fit: cover !important;
margin-right: 12px !important;
}
/* Panel formulaire */
.bootbox-confirm .kraland-form-zone .panel {
border-radius: 8px !important;
margin-bottom: 16px !important;
}
/* Inputs et textarea */
.bootbox-confirm .kraland-form-zone input[type="text"],
.bootbox-confirm .kraland-form-zone textarea,
.bootbox-confirm .kraland-form-zone select {
font-size: 16px !important; /* Évite zoom iOS */
border-radius: 6px !important;
}
.bootbox-confirm .kraland-form-zone textarea {
min-height: 120px !important;
}
}
.bootbox-confirm textarea.form-control:focus {
border-color: var(--kr-primary);
box-shadow: 0 0 0 3px var(--kr-focus-ring);
outline: none;
}
/* ============================================================================
9d. AMÉLIORATIONS UX MODAL ORDRE (MOBILE ONLY)
============================================================================ */
@media (width <= 768px) {
/* === UX #1: ALERTE REPLIABLE === */
.bootbox-confirm .kr-alert-collapsible {
padding: 0 !important;
margin-bottom: 12px !important;
border-radius: 8px !important;
overflow: hidden !important;
}
.bootbox-confirm .kr-alert-toggle {
width: 100% !important;
padding: 12px 16px !important;
background: #e3f2fd !important;
border: none !important;
color: #1565c0 !important;
font-size: 15px !important;
font-weight: 600 !important;
text-align: left !important;
display: flex !important;
align-items: center !important;
gap: 8px !important;
cursor: pointer !important;
transition: background 0.2s ease !important;
}
.bootbox-confirm .kr-alert-toggle:active {
background: #bbdefb !important;
}
.bootbox-confirm .kr-alert-toggle i {
font-size: 18px !important;
}
.bootbox-confirm .kr-alert-content {
padding: 12px 16px !important;
background: white !important;
border-top: 1px solid #e0e0e0 !important;
animation: slide-down 0.2s ease !important;
}
@keyframes slide-down {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* === UX #2: NAV-TABS ÉTAT ACTIF RENFORCÉ === */
.bootbox-confirm .nav.nav-tabs li.active a {
background: linear-gradient(135deg, #428bca 0%, #3071a9 100%) !important;
border-color: #428bca !important;
color: white !important;
font-weight: 600 !important;
box-shadow: 0 2px 8px rgba(66, 139, 202, 0.4) !important;
transform: translateY(-1px) !important;
}
.bootbox-confirm .nav.nav-tabs li a:active {
transform: translateY(0) !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.2) !important;
}
/* === UX #3: TEXTAREA AGRANDI === */
.bootbox-confirm .kraland-form-zone textarea#message {
min-height: 160px !important;
resize: vertical !important;
font-size: 16px !important;
line-height: 1.5 !important;
padding: 12px !important;
}
/* === UX #4: FOOTER EN BADGES === */
.bootbox-confirm .kr-action-badges {
display: flex !important;
flex-wrap: wrap !important;
gap: 8px !important;
align-items: center !important;
}
.bootbox-confirm .kr-badge {
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
padding: 6px 12px !important;
border-radius: 16px !important;
font-size: 13px !important;
font-weight: 500 !important;
white-space: nowrap !important;
}
.bootbox-confirm .kr-badge i {
font-size: 14px !important;
}
.bootbox-confirm .kr-badge-cost {
background: #fff3cd !important;
color: #856404 !important;
border: 1px solid #ffeaa7 !important;
}
.bootbox-confirm .kr-badge-duration {
background: #d1ecf1 !important;
color: #0c5460 !important;
border: 1px solid #b8daff !important;
}
.bootbox-confirm .kr-badge-potential {
background: #f8d7da !important;
color: #721c24 !important;
border: 1px solid #f5c6cb !important;
}
/* === UX #5: BOUTON OK RENFORCÉ === */
.bootbox-confirm .kr-btn-primary-enhanced {
background: linear-gradient(135deg, #5cb85c 0%, #449d44 100%) !important;
border-color: #449d44 !important;
color: white !important;
font-weight: 700 !important;
font-size: 18px !important;
box-shadow: 0 4px 12px rgba(92, 184, 92, 0.4) !important;
transition: all 0.2s ease !important;
}
.bootbox-confirm .kr-btn-primary-enhanced:active {
transform: translateY(2px) !important;
box-shadow: 0 2px 6px rgba(92, 184, 92, 0.3) !important;
}
.bootbox-confirm .kr-btn-secondary-subtle {
background: #f5f5f5 !important;
border-color: #ddd !important;
color: #666 !important;
font-weight: 500 !important;
}
.bootbox-confirm .kr-btn-secondary-subtle:active {
background: #e0e0e0 !important;
}
/* === UX #6: SELECT GROUPÉ === */
.bootbox-confirm .kraland-modal-header select optgroup {
font-weight: 700 !important;
font-size: 14px !important;
color: #428bca !important;
padding: 8px 0 !important;
}
.bootbox-confirm .kraland-modal-header select option {
padding: 8px 12px !important;
font-size: 15px !important;
}
}
/* 7. ALERT D'AIDE */
.bootbox-confirm .alert {
font-size: 0.875rem;
padding: 12px;
margin-bottom: var(--mobile-spacing-lg);
border-radius: var(--mobile-radius);
background: var(--kr-alert-info-bg);
border: 1px solid var(--kr-alert-info-border);
}
.bootbox-confirm .alert .close {
font-size: 1.5rem;
line-height: 1;
opacity: 0.5;
}
/* 8. FOOTER STICKY */
.bootbox-confirm .modal-footer {
position: sticky;
bottom: 0;
background: var(--kr-bg-surface);
border-top: 2px solid var(--kr-border-strong);
padding: var(--mobile-spacing-lg);
z-index: 100;
box-shadow: 0 -4px 8px rgba(0,0,0,0.05);
display: flex;
flex-direction: column;
gap: var(--mobile-spacing-md);
}
/* Info coût/durée/potentiel */
.bootbox-confirm .modal-footer p {
font-size: 0.9rem;
font-weight: 600;
color: var(--kr-text-primary);
margin-bottom: 0;
padding: var(--mobile-spacing-sm) var(--mobile-spacing-md);
background: var(--kr-bg-elevated);
border-radius: var(--mobile-radius);
border-left: 3px solid var(--kr-primary);
}
/* Boutons footer */
.bootbox-confirm .modal-footer .btn {
min-height: var(--mobile-touch-target);
min-width: 100px;
font-size: 1rem;
padding: 10px 24px;
border-radius: var(--mobile-radius);
font-weight: 500;
}
.bootbox-confirm .modal-footer > div {
display: flex;
gap: var(--mobile-spacing-md);
justify-content: flex-end;
}
} /* Fin @media (width < 768px) pour BOOTBOX ORDER MODAL */
/*
Pagination est utilisé dans les rapports
*/
.pagination > li.active > a{
color: var(--kr-surface);
background-color: var(--kr-primary-dark);
border-color: var(--kr-primary-dark);
}
.pagination > li > a{
color: var(--kr-primary);
}
.pagination > li.active > a:hover{
color: var(--kr-surface);
background-color: var(--kr-primary);
border-color: var(--kr-primary);
}
.pagination > li > a:hover{
color: var(--kr-primary);
}
/*
.bg-primary est utilisé dans les rapports
*/
.bg-primary {
background-color: var(--kr-primary) !important;
color: var(--kr-surface) !important;
}
a:link,
a:visited {
color: var(--kr-highlight);
text-decoration: none;
transition: color .12s ease, opacity .12s ease;
}
a:hover,
a:focus {
color: var(--kr-primary-dark);
text-decoration: underline;
outline: none;
}
button.btn-primary,
a.btn-primary {
background-color: var(--kr-primary);
border-color: var(--kr-primary-dark);
color: var(--kr-highlight-reverse);
box-shadow: 0 6px 18px var(--kr-btn-shadow);
}
button.btn-primary:hover,
button.btn-primary:focus,
a.btn-primary:hover,
a.btn-primary:focus {
background-color: var(--kr-primary-dark);
}
/* ============================================================================
HIDE UNWANTED HR ELEMENT
Supprime la ligne horizontale bleue dans le liste des ordres de la fiche de personnage
============================================================================ */
hr[style*="border-top: 1px solid"][style*="337ab7"] {
display: none !important;
}
/* ============================================================================
10. FOOTER - BACK TO TOP
Position the back-to-top button on the right side of the footer
============================================================================ */
footer .container.white {
position: relative;
}
.container.white .kraland-back-to-top {
position: fixed; /* Fixed au lieu d'absolute pour ne pas ajouter de hauteur au footer */
right: 20px;
bottom: 20px;
z-index: 1000;
transform: none; /* Pas de transform nécessaire avec fixed */
}
/* ============================================================================
11. DASHBOARD FLEX CARDS - Groupes de joueurs
Système de cartes en grid avec toutes les informations visibles
============================================================================ */
.dashboard.dashboard-flex {
display: flex;
flex-direction: column;
gap: 15px;
}
/* Sections (Mon groupe / Autres personnages) */
.dashboard-section {
background: var(--kr-surface);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgb(0,0,0,0.08);
}
.dashboard-section-header {
background: var(--kr-primary);
color: var(--kr-white);
padding: 3px 8px;
font-size: 9px;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
}
/* Boutons de groupe dans l'en-tête */
.dashboard-group-buttons {
display: flex;
gap: 6px;
}
.dashboard-group-buttons .btn {
background-color: rgb(255, 255, 255, 0.15);
border: 1px solid rgb(255, 255, 255, 0.25);
color: var(--kr-white);
padding: 4px 8px;
font-size: 12px;
border-radius: 4px;
transition: all 0.2s ease;
}
.dashboard-group-buttons .btn:hover {
background-color: rgb(255, 255, 255, 0.25);
border-color: rgb(255, 255, 255, 0.4);
transform: scale(1.05);
}
.dashboard-group-buttons .btn i {
font-size: 12px;
}
/* Titre du groupe */
.dashboard-group-title {
flex: 1;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Grilles de cartes */
.dashboard-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 10px;
padding: 12px;
}
.dashboard-cards-large {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* Cartes individuelles */
.dashboard-card {
background: var(--kr-white);
border: 1px solid rgb(0,0,0,0.08);
border-radius: 6px;
overflow: hidden;
transition: all 0.2s ease;
position: relative;
display: flex;
flex-direction: column;
}
.dashboard-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgb(0,0,0,0.12);
border-color: var(--kr-primary);
}
.dashboard-card-large {
min-height: 90px;
}
/* Lien wrapper */
.dashboard-card-link {
display: flex;
flex-direction: column;
text-decoration: none;
color: inherit;
flex: 1;
}
.dashboard-card-link:hover {
text-decoration: none;
}
/* Header avec avatar et nom */
.dashboard-card-header {
display: flex;
align-items: center;
background: rgb(0,0,0,0.02);
}
/* Wrapper pour l'avatar avec cercle de PV */
.dashboard-card-avatar-wrapper {
position: relative;
width: 82px;
height: 82px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
/* Cercle SVG de progression des PV */
.dashboard-card-hp-circle {
position: absolute;
top: 0;
left: 0;
width: 82px;
height: 82px;
pointer-events: none;
z-index: 1;
}
.dashboard-card-avatar {
width: 70px;
height: 70px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--kr-primary);
flex-shrink: 0;
position: relative;
z-index: 2;
}
.dashboard-card-large .dashboard-card-avatar {
width: 70px;
height: 70px;
}
/* Conteneur pour le drapeau et le nom */
.dashboard-card-name-container {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
min-width: 0;
}
/* Drapeau de nationalité dans le header */
.dashboard-card-world {
width: 20px;
height: 10px;
object-fit: contain;
flex-shrink: 0;
}
.dashboard-card-name {
font-size: 11px;
font-weight: 600;
color: var(--kr-text);
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
flex: 1;
}
.dashboard-card-large .dashboard-card-name {
font-size: 13px;
}
/* Body avec statut uniquement (monde dans les actions) */
.dashboard-card-body {
padding: 0 8px 5px;
display: flex;
flex-direction: column;
gap: 3px;
flex: 1;
min-height: 0;
}
.dashboard-card-status {
font-size: 9px;
color: var(--kr-muted);
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.dashboard-card-large .dashboard-card-status {
font-size: 11px;
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
/* Barre de HP */
.dashboard-card-hp {
height: 6px;
background: rgb(0,0,0,0.08);
position: relative;
margin: 0;
}
.dashboard-card-hp-fill {
height: 100%;
transition: width 0.3s ease, background-color 0.3s ease;
border-radius: 0;
}
/* Badge PNJ */
.dashboard-card-pnj {
position: absolute;
top: 8px;
right: 8px;
background: var(--kr-badge-pnj);
color: var(--kr-white);
font-size: 8px;
font-weight: 600;
padding: 2px 5px;
border-radius: 3px;
text-transform: uppercase;
letter-spacing: 0.3px;
z-index: 1;
}
/* Boutons d'actions individuelles par personnage */
.dashboard-card-actions {
position: absolute;
bottom: 4px;
right: 4px;
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
opacity: 0.7;
transition: opacity 0.2s ease;
z-index: 2;
}
.dashboard-card:hover .dashboard-card-actions {
opacity: 1;
}
/* Adapter les divs internes pour qu'elles soient empilées verticalement */
.dashboard-card-actions > div {
display: flex;
flex-direction: column;
gap: 2px;
margin: 0 !important;
padding: 0 !important;
height: auto !important;
}
.dashboard-card-actions > div > div {
margin: 0 !important;
padding: 0 !important;
height: auto !important;
}
/* Liens d'action compacts */
.dashboard-card-actions a {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px 6px !important;
background-color: rgb(0, 0, 0, 0.6);
border-radius: 3px;
transition: background-color 0.2s ease;
min-width: 24px;
height: 24px;
}
.dashboard-card-actions a:hover {
background-color: rgb(0, 0, 0, 0.8);
}
.dashboard-card-actions a i {
color: var(--kr-white);
font-size: 12px;
margin: 0;
}
/* Responsive */
@media (width <= 768px) {
.dashboard-cards-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
}
.dashboard-cards-large {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
}
/* ============================================================================
12. EDITEUR DE TEXTE
Styles pour les boutons de l'éditeur BBCode
============================================================================ */
/* Style de base pour tous les boutons de l'éditeur, sauf la palette de couleurs */
.btn-toolbar .btn:not(.dropdown-menu *) {
background-color: var(--kr-surface) !important;
background-image: none !important;
color: var(--kr-primary) !important;
border: 1px solid rgb(0,0,0,0.06) !important;
box-shadow: none !important;
}
.btn-toolbar .btn:not(.dropdown-menu *) i,
.btn-toolbar .btn:not(.dropdown-menu *) .fa,
.btn-toolbar .btn:not(.dropdown-menu *) .fas,
.btn-toolbar .btn:not(.dropdown-menu *) .far {
color: inherit !important;
}
/* ============================================================================
13. DARK MODE (Tous les thèmes)
Styles mutualisés pour tous les thèmes en mode sombre
============================================================================ */
/* === BODY & BACKGROUNDS === */
html[class*="-dark"] body {
background-color: var(--kr-bg-page);
color: var(--kr-text-primary);
}
/* === PANELS === */
html[class*="-dark"] .panel,
html[class*="-dark"] .panel-default {
background-color: var(--kr-bg-surface);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .panel-heading {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
html[class*="-dark"] .panel-body {
background-color: var(--kr-bg-surface);
color: var(--kr-text-primary);
}
html[class*="-dark"] .panel-footer {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
}
/* === WELLS === */
html[class*="-dark"] .well {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
/* === THEME SELECTOR TAMPERMONKEY === */
html[class*="-dark"] .kr-tamper-theme label {
color: var(--kr-text-primary);
}
html[class*="-dark"] .kr-tamper-theme .control-label {
color: var(--kr-text-secondary);
}
html[class*="-dark"] .kr-tamper-theme h4 {
color: var(--kr-text-primary);
}
/* === MODALS === */
html[class*="-dark"] .modal-content {
background-color: var(--kr-bg-surface);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .modal-header,
html[class*="-dark"] .modal-body,
html[class*="-dark"] .modal-footer {
background-color: var(--kr-bg-surface);
color: var(--kr-text-primary);
border-color: var(--kr-border-default);
}
/* === FORMS === */
html[class*="-dark"] .form-control {
background-color: var(--kr-form-bg);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
html[class*="-dark"] .form-control::placeholder {
color: var(--kr-text-muted);
}
html[class*="-dark"] .form-control:focus {
background-color: var(--kr-form-bg-focus);
border-color: var(--kr-primary);
color: var(--kr-text-primary);
}
html[class*="-dark"] .form-group label,
html[class*="-dark"] label {
color: var(--kr-text-primary);
}
html[class*="-dark"] .input-group-addon {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
/* === BUTTONS === */
html[class*="-dark"] .btn-primary {
background-color: var(--kr-primary);
border-color: var(--kr-primary-dark);
color: var(--kr-text-primary);
}
html[class*="-dark"] .btn-primary:hover,
html[class*="-dark"] .btn-primary:focus,
html[class*="-dark"] .btn-primary:active {
background-color: var(--kr-primary-dark);
border-color: var(--kr-primary-dark);
color: var(--kr-text-primary);
}
html[class*="-dark"] .btn-default {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
html[class*="-dark"] .btn-default:hover {
background-color: var(--kr-bg-active);
border-color: var(--kr-border-strong);
}
html[class*="-dark"] .btn-default:focus,
html[class*="-dark"] .btn-default:active {
background-color: var(--kr-bg-hover);
border-color: var(--kr-border-strong);
color: var(--kr-text-primary);
}
/* === LIST GROUPS === */
html[class*="-dark"] .list-group-item {
background-color: var(--kr-bg-surface);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
html[class*="-dark"] .list-group-item:hover {
background-color: var(--kr-bg-hover);
}
html[class*="-dark"] .list-group-item-heading {
color: var(--kr-text-primary);
}
html[class*="-dark"] .list-group-item-text {
color: var(--kr-text-secondary);
}
/* === TABLES === */
html[class*="-dark"] .table {
color: var(--kr-text-primary);
}
html[class*="-dark"] .table > thead > tr > th,
html[class*="-dark"] .table > tbody > tr > th,
html[class*="-dark"] .table > tfoot > tr > th {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-default);
color: var(--kr-text-primary);
}
html[class*="-dark"] .table > tbody > tr > td {
border-color: var(--kr-border-default);
}
html[class*="-dark"] .table-striped > tbody > tr:nth-of-type(odd) {
background-color: var(--kr-bg-hover);
}
/* Override for tables with forum-like styling */
html[class*="-dark"] .table.table-striped > tbody > tr {
background-color: var(--kr-bg-page);
}
html[class*="-dark"] .table.table-striped > tbody > tr:nth-of-type(odd) {
background-color: var(--kr-bg-elevated);
}
html[class*="-dark"] .table-hover > tbody > tr:hover {
background-color: var(--kr-bg-active);
}
/* === DROPDOWNS === */
html[class*="-dark"] .dropdown-menu {
background-color: var(--kr-bg-elevated);
border-color: var(--kr-border-strong);
}
html[class*="-dark"] .dropdown-menu > li > a {
color: var(--kr-text-primary);
}
html[class*="-dark"] .dropdown-menu > li > a:hover,
html[class*="-dark"] .dropdown-menu > li > a:focus {
background-color: var(--kr-bg-hover);
color: var(--kr-text-primary);
}
html[class*="-dark"] .dropdown-menu > .active > a,
html[class*="-dark"] .dropdown-menu > .active > a:hover,
html[class*="-dark"] .dropdown-menu > .active > a:focus {
background-color: var(--kr-primary);
color: var(--kr-text-inverse);
}
/* === NAVIGATION === */
html[class*="-dark"] .nav-tabs {
border-color: var(--kr-border-default);
}
html[class*="-dark"] .nav-tabs > li > a {
color: var(--kr-text-primary);
}
html[class*="-dark"] .nav-tabs > li > a:hover {
background-color: var(--kr-bg-hover);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .nav-tabs > li.active > a,
html[class*="-dark"] .nav-tabs > li.active > a:hover,
html[class*="-dark"] .nav-tabs > li.active > a:focus {
background-color: var(--kr-bg-surface);
border-color: var(--kr-border-default) var(--kr-border-default) transparent;
color: var(--kr-text-primary);
}
/* === ALERTS === */
html[class*="-dark"] .alert {
border-color: var(--kr-border-default);
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] .alert * {
color: inherit !important;
}
html[class*="-dark"] .alert a {
color: var(--kr-primary) !important;
text-decoration: underline;
}
html[class*="-dark"] .alert-info {
background-color: rgb(122, 82, 64, 0.15);
border-color: rgb(122, 82, 64, 0.3);
color: var(--kr-text-primary);
}
html[class*="-dark"] .alert-success {
background-color: rgb(34, 197, 94, 0.15);
border-color: rgb(34, 197, 94, 0.3);
color: var(--kr-text-primary);
}
html[class*="-dark"] .alert-warning {
background-color: rgb(245, 158, 11, 0.15);
border-color: rgb(245, 158, 11, 0.3);
color: var(--kr-text-primary);
}
html[class*="-dark"] .alert-danger {
background-color: rgb(239, 68, 68, 0.15);
border-color: rgb(239, 68, 68, 0.3);
color: var(--kr-text-primary);
}
/* === PROGRESS === */
html[class*="-dark"] .progress {
background-color: var(--kr-bg-elevated);
}
/* === DASHBOARD CARDS === */
html[class*="-dark"] .dashboard-card {
background: var(--kr-bg-surface);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .dashboard-card:hover {
box-shadow: var(--kr-shadow-md);
}
html[class*="-dark"] .dashboard-card-header {
background: var(--kr-bg-hover);
}
html[class*="-dark"] .dashboard-card-name {
color: var(--kr-text-primary);
}
html[class*="-dark"] .dashboard-card-status {
color: var(--kr-text-muted);
}
html[class*="-dark"] .dashboard-section {
background: var(--kr-bg-surface);
}
html[class*="-dark"] .dashboard-card-hp {
background: var(--kr-bg-elevated);
}
/* === MINI-CHAT === */
html[class*="-dark"] .chat li {
border-bottom-color: var(--kr-border-default);
}
html[class*="-dark"] .panel-body-scroll {
background-color: var(--kr-bg-surface);
}
html[class*="-dark"] #flap {
background-color: var(--kr-bg-elevated);
}
html[class*="-dark"] #flap a.open,
html[class*="-dark"] #flap a.closed {
background: var(--kr-bg-elevated);
color: var(--kr-text-primary);
}
/* === MAP === */
html[class*="-dark"] .map-box {
background-color: var(--kr-bg-surface);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .map-box-title {
background-color: var(--kr-primary);
color: var(--kr-text-inverse);
}
html[class*="-dark"] .map-box-content {
color: var(--kr-text-primary);
}
html[class*="-dark"] .map-box-bottom {
background-color: var(--kr-primary);
color: var(--kr-text-inverse);
}
/* === CLASSES KRALAND .f1-.f9 (couleurs nations) === */
html[class*="-dark"] .f1 { color: var(--kr-nation-1); }
html[class*="-dark"] .f2 { color: var(--kr-nation-2); }
html[class*="-dark"] .f3 { color: var(--kr-nation-3); }
html[class*="-dark"] .f4 { color: var(--kr-nation-4); }
html[class*="-dark"] .f5 { color: var(--kr-nation-5); }
html[class*="-dark"] .f6 { color: var(--kr-nation-6); }
html[class*="-dark"] .f7 { color: var(--kr-nation-7); }
html[class*="-dark"] .f8 { color: var(--kr-nation-8); }
html[class*="-dark"] .f9 { color: var(--kr-nation-9); }
/* === CLASSES KRALAND .c1-.c10 (fonds nations) === */
html[class*="-dark"] .c1 { background-color: var(--kr-nation-bg-1); }
html[class*="-dark"] .c2 { background-color: var(--kr-nation-bg-2); }
html[class*="-dark"] .c3 { background-color: var(--kr-nation-bg-3); }
html[class*="-dark"] .c4 { background-color: var(--kr-nation-bg-4); }
html[class*="-dark"] .c5 { background-color: var(--kr-nation-bg-5); }
html[class*="-dark"] .c6 { background-color: var(--kr-nation-bg-6); }
html[class*="-dark"] .c7 { background-color: var(--kr-nation-bg-7); }
html[class*="-dark"] .c8 { background-color: var(--kr-nation-bg-8); }
html[class*="-dark"] .c9 { background-color: var(--kr-nation-bg-9); }
html[class*="-dark"] .c10 { background-color: var(--kr-nation-bg-10); }
/* === COULEURS HTML KRALAND (balises ) === */
/* Surcharge des couleurs spécifiques utilisées par Kraland pour le dark mode */
/* Ajustement minimal pour conserver les teintes d'origine */
html[class*="-dark"] font[color="#f4ac00"],
html[class*="-dark"] font[color="f4ac00"] {
color: #ffbe33 !important; /* yellow - Légèrement éclairci, garde la teinte or */
}
html[class*="-dark"] font[color="#f77400"],
html[class*="-dark"] font[color="f77400"] {
color: #ff8833 !important; /* orange - Éclairci mais garde l'orange vif */
}
html[class*="-dark"] font[color="#ed6161"],
html[class*="-dark"] font[color="ed6161"] {
color: #ff7a7a !important; /* fuchsia - Légèrement éclairci, garde le rose-rouge */
}
html[class*="-dark"] font[color="#d50000"],
html[class*="-dark"] font[color="d50000"] {
color: #ff3333 !important; /* red - Rouge vif éclairci */
}
html[class*="-dark"] font[color="olive"] {
color: #b3b333 !important; /* olive - Éclairci mais garde le jaune-vert olive */
}
html[class*="-dark"] font[color="#219c5a"],
html[class*="-dark"] font[color="219c5a"] {
color: #33cc77 !important; /* lightgreen - Éclairci, garde le vert vif */
}
html[class*="-dark"] font[color="#006f00"],
html[class*="-dark"] font[color="006f00"] {
color: #00bb00 !important; /* green - Vert pur éclairci */
}
html[class*="-dark"] font[color="teal"] {
color: #33cccc !important; /* teal - Teal éclairci */
}
html[class*="-dark"] font[color="#5577bc"],
html[class*="-dark"] font[color="5577bc"] {
color: #7799dd !important; /* lightblue - Légèrement éclairci, garde le bleu */
}
html[class*="-dark"] font[color="#2b2be4"],
html[class*="-dark"] font[color="2b2be4"] {
color: #5555ff !important; /* blue - Bleu vif éclairci */
}
html[class*="-dark"] font[color="navy"] {
color: #5555cc !important; /* navy - Navy éclairci mais garde le bleu foncé */
}
html[class*="-dark"] font[color="purple"] {
color: #cc55cc !important; /* purple - Violet éclairci */
}
html[class*="-dark"] font[color="#4B0082"],
html[class*="-dark"] font[color="4B0082"],
html[class*="-dark"] font[color="4b0082"] {
color: #8855cc !important; /* indigo - Indigo éclairci, garde le violet-bleu */
}
html[class*="-dark"] font[color="maroon"] {
color: #cc5555 !important; /* maroon - Bordeaux éclairci */
}
html[class*="-dark"] font[color="#5e432d"],
html[class*="-dark"] font[color="5e432d"] {
color: #aa7755 !important; /* brown - Marron éclairci, garde la teinte chaude */
}
html[class*="-dark"] font[color="gray"] {
color: #aaaaaa !important; /* gray - Gris éclairci */
}
html[class*="-dark"] font[color="#5a5a5a"],
html[class*="-dark"] font[color="5a5a5a"] {
color: #999999 !important; /* darkgray - Éclairci mais garde le gris moyen */
}
html[class*="-dark"] font[color="#000000"],
html[class*="-dark"] font[color="000000"] {
color: #cccccc !important; /* black - Gris clair (noir impossible en dark) */
}
/* === CLASSES DE COULEURS === */
html[class*="-dark"] .red {
color: #ff3333 !important; /* red - Rouge vif éclairci */
}
html[class*="-dark"] .blue {
color: #3388ff !important; /* blue - Bleu France, bon contraste */
}
/* === COULEURS PALETTE DE SÉLECTION (boutons background-color) === */
/* Surcharge des background-color des boutons de la palette de couleurs */
html[class*="-dark"] [style*="background-color:#f4ac00"],
html[class*="-dark"] [style*="background-color: #f4ac00"] {
background-color: #ffbe33 !important; /* yellow */
}
html[class*="-dark"] [style*="background-color:#f77400"],
html[class*="-dark"] [style*="background-color: #f77400"] {
background-color: #ff8833 !important; /* orange */
}
html[class*="-dark"] [style*="background-color:#ed6161"],
html[class*="-dark"] [style*="background-color: #ed6161"] {
background-color: #ff7a7a !important; /* fuchsia */
}
html[class*="-dark"] [style*="background-color:#d50000"],
html[class*="-dark"] [style*="background-color: #d50000"] {
background-color: #ff3333 !important; /* red */
}
html[class*="-dark"] [style*="background-color:#808000"],
html[class*="-dark"] [style*="background-color: #808000"] {
background-color: #b3b333 !important; /* olive */
}
html[class*="-dark"] [style*="background-color:#219c5a"],
html[class*="-dark"] [style*="background-color: #219c5a"] {
background-color: #33cc77 !important; /* lightgreen */
}
html[class*="-dark"] [style*="background-color:#006f00"],
html[class*="-dark"] [style*="background-color: #006f00"] {
background-color: #00bb00 !important; /* green */
}
html[class*="-dark"] [style*="background-color:#008080"],
html[class*="-dark"] [style*="background-color: #008080"] {
background-color: #33cccc !important; /* teal */
}
html[class*="-dark"] [style*="background-color:#5577bc"],
html[class*="-dark"] [style*="background-color: #5577bc"] {
background-color: #7799dd !important; /* lightblue */
}
html[class*="-dark"] [style*="background-color:#2b2be4"],
html[class*="-dark"] [style*="background-color: #2b2be4"] {
background-color: #5555ff !important; /* blue */
}
html[class*="-dark"] [style*="background-color:#000080"],
html[class*="-dark"] [style*="background-color: #000080"] {
background-color: #5555cc !important; /* navy */
}
html[class*="-dark"] [style*="background-color:#800080"],
html[class*="-dark"] [style*="background-color: #800080"] {
background-color: #cc55cc !important; /* purple */
}
html[class*="-dark"] [style*="background-color:#4B0082"],
html[class*="-dark"] [style*="background-color: #4B0082"],
html[class*="-dark"] [style*="background-color:#4b0082"],
html[class*="-dark"] [style*="background-color: #4b0082"] {
background-color: #8855cc !important; /* indigo */
}
html[class*="-dark"] [style*="background-color:#800000"],
html[class*="-dark"] [style*="background-color: #800000"] {
background-color: #cc5555 !important; /* maroon */
}
html[class*="-dark"] [style*="background-color:#5e432d"],
html[class*="-dark"] [style*="background-color: #5e432d"] {
background-color: #aa7755 !important; /* brown */
}
html[class*="-dark"] [style*="background-color:#808080"],
html[class*="-dark"] [style*="background-color: #808080"] {
background-color: #aaaaaa !important; /* gray */
}
html[class*="-dark"] [style*="background-color:#5a5a5a"],
html[class*="-dark"] [style*="background-color: #5a5a5a"] {
background-color: #999999 !important; /* darkgray */
}
html[class*="-dark"] [style*="background-color:#000000"],
html[class*="-dark"] [style*="background-color: #000000"] {
background-color: #cccccc !important; /* black */
}
/* === SCROLLBARS === */
html[class*="-dark"] ::-webkit-scrollbar-track {
background-color: var(--kr-bg-page);
}
html[class*="-dark"] ::-webkit-scrollbar-thumb {
background-color: var(--kr-text-muted);
}
html[class*="-dark"] ::-webkit-scrollbar-thumb:hover {
background-color: var(--kr-text-secondary);
}
/* === SELECTION === */
html[class*="-dark"] ::selection {
background-color: var(--kr-primary);
color: var(--kr-text-inverse);
}
/* === FOOTER === */
html[class*="-dark"] footer {
background-color: var(--kr-bg-elevated);
border-top: 1px solid var(--kr-border-default);
}
html[class*="-dark"] .footer-quote {
color: var(--kr-text-secondary);
}
/* === SKILLS PANEL === */
html[class*="-dark"] #skills-panel {
background-color: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
}
/* === SEPARATORS === */
html[class*="-dark"] hr {
display: none;
}
/* === CAROUSEL === */
html[class*="-dark"] .carousel-control {
color: var(--kr-primary);
opacity: 0.7;
}
html[class*="-dark"] .carousel-control:hover,
html[class*="-dark"] .carousel-control:focus {
color: var(--kr-highlight);
opacity: 0.9;
}
html[class*="-dark"] .carousel-indicators li {
background-color: var(--kr-border-default);
border-color: var(--kr-border-default);
}
html[class*="-dark"] .carousel-indicators .active {
background-color: var(--kr-primary);
border-color: var(--kr-primary);
}
/* === LIENS === */
html[class*="-dark"] a:link,
html[class*="-dark"] a:visited {
color: var(--kr-highlight);
}
html[class*="-dark"] a:hover,
html[class*="-dark"] a:focus {
color: var(--kr-primary);
}
/* === BOUTONS DE NAVIGATION PREV/NEXT === */
html[class*="-dark"] a.prev,
html[class*="-dark"] a.next {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] a.prev:hover,
html[class*="-dark"] a.next:hover {
background-color: var(--kr-bg-hover) !important;
border-color: var(--kr-border-strong) !important;
color: var(--kr-highlight) !important;
}
/* === BOUTONS WARNING === */
/* Adaptation des boutons warning pour le thème sombre */
html[class*="-dark"] .btn-warning {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary-dark) !important;
color: var(--kr-text-inverse) !important;
}
html[class*="-dark"] .btn-warning:hover {
background-color: var(--kr-highlight) !important;
border-color: var(--kr-primary) !important;
}
/* === ICÔNES DE COMPÉTENCES ET CARACTÉRISTIQUES === */
/* Réduction du contraste des icônes blanches */
html[class*="-dark"] img[src*="/mat/94/"] {
filter: none;
opacity: 0.85;
}
html[class*="-dark"] img[src*="/mat/94/"]:hover {
filter: none;
opacity: 1;
}
/* === ICÔNES DE BÂTIMENTS === */
/* Réduction du contraste des icônes blanches */
html[class*="-dark"] img[src*="/bat/bat"] {
filter: none;
opacity: 0.85;
}
html[class*="-dark"] img[src*="/bat/bat"]:hover {
filter: none;
opacity: 1;
}
/* === ICÔNES DE VOCATIONS === */
/* Réduction du contraste des icônes blanches */
html[class*="-dark"] img[src*="/voc/"] {
filter: none;
opacity: 0.85;
}
html[class*="-dark"] img[src*="/voc/"]:hover {
filter: none;
opacity: 1;
}
/* ============================================================================
HORLOGE À DOUBLE TOUR (0-48H)
============================================================================ */
/* Système d'horloge circulaire qui supporte jusqu'à 48 heures avec deux tours de cadran.
Le premier tour (0-24h) s'affiche sur le cercle principal.
Le deuxième tour (24-48h) s'affiche sur un cercle extérieur plus visible.
*/
/* Container de l'horloge */
.c100 {
position: relative !important;
width: 80px !important;
height: 80px !important;
border-radius: 50% !important;
background-color: var(--kr-bg-elevated, #f5f5f5) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
overflow: visible !important;
}
/* Masquer l'ancien système à base de .slice, .bar, .fill */
.c100 .slice,
.c100 .bar,
.c100 .fill {
display: none !important;
}
/* Texte de l'heure au centre */
.c100 > span {
position: relative !important;
z-index: 10 !important;
font-size: 20px !important;
font-weight: 600 !important;
color: var(--kr-text-primary, #333) !important;
text-align: center !important;
}
/* Fond circulaire après */
.c100::after {
content: '' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
width: 68px !important;
height: 68px !important;
border-radius: 50% !important;
background-color: var(--kr-bg-elevated, #f5f5f5) !important;
z-index: 5 !important;
}
/* Cercle de progression - Premier tour (0-24h) */
.c100::before {
content: '' !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
border-radius: 50% !important;
background: conic-gradient(
var(--clock-color, #8B0000) 0deg,
var(--clock-color, #8B0000) calc(var(--clock-deg, 0) * 1deg),
transparent calc(var(--clock-deg, 0) * 1deg),
transparent 360deg
) !important;
z-index: 1 !important;
transition: none !important;
}
/* Deuxième tour (24-48h) - Cercle extérieur avec bordure reprenant la couleur de l'horloge */
.c100[data-second-lap="true"]::before {
box-shadow:
0 0 0 1px var(--kr-bg-elevated, #f5f5f5),
0 0 0 5px var(--clock-color, #32CD32),
inset 0 0 0 2px var(--clock-color, #32CD32) !important;
}
/* Classes de pourcentage p0 à p100 pour la compatibilité */
.c100.p0 { --clock-deg: 0; }
.c100.p1 { --clock-deg: 3.6; }
.c100.p2 { --clock-deg: 7.2; }
.c100.p3 { --clock-deg: 10.8; }
.c100.p4 { --clock-deg: 14.4; }
.c100.p5 { --clock-deg: 18; }
.c100.p6 { --clock-deg: 21.6; }
.c100.p7 { --clock-deg: 25.2; }
.c100.p8 { --clock-deg: 28.8; }
.c100.p9 { --clock-deg: 32.4; }
.c100.p10 { --clock-deg: 36; }
.c100.p11 { --clock-deg: 39.6; }
.c100.p12 { --clock-deg: 43.2; }
.c100.p13 { --clock-deg: 46.8; }
.c100.p14 { --clock-deg: 50.4; }
.c100.p15 { --clock-deg: 54; }
.c100.p16 { --clock-deg: 57.6; }
.c100.p17 { --clock-deg: 61.2; }
.c100.p18 { --clock-deg: 64.8; }
.c100.p19 { --clock-deg: 68.4; }
.c100.p20 { --clock-deg: 72; }
.c100.p21 { --clock-deg: 75.6; }
.c100.p22 { --clock-deg: 79.2; }
.c100.p23 { --clock-deg: 82.8; }
.c100.p24 { --clock-deg: 86.4; }
.c100.p25 { --clock-deg: 90; }
.c100.p26 { --clock-deg: 93.6; }
.c100.p27 { --clock-deg: 97.2; }
.c100.p28 { --clock-deg: 100.8; }
.c100.p29 { --clock-deg: 104.4; }
.c100.p30 { --clock-deg: 108; }
.c100.p31 { --clock-deg: 111.6; }
.c100.p32 { --clock-deg: 115.2; }
.c100.p33 { --clock-deg: 118.8; }
.c100.p34 { --clock-deg: 122.4; }
.c100.p35 { --clock-deg: 126; }
.c100.p36 { --clock-deg: 129.6; }
.c100.p37 { --clock-deg: 133.2; }
.c100.p38 { --clock-deg: 136.8; }
.c100.p39 { --clock-deg: 140.4; }
.c100.p40 { --clock-deg: 144; }
.c100.p41 { --clock-deg: 147.6; }
.c100.p42 { --clock-deg: 151.2; }
.c100.p43 { --clock-deg: 154.8; }
.c100.p44 { --clock-deg: 158.4; }
.c100.p45 { --clock-deg: 162; }
.c100.p46 { --clock-deg: 165.6; }
.c100.p47 { --clock-deg: 169.2; }
.c100.p48 { --clock-deg: 172.8; }
.c100.p49 { --clock-deg: 176.4; }
.c100.p50 { --clock-deg: 180; }
.c100.p51 { --clock-deg: 183.6; }
.c100.p52 { --clock-deg: 187.2; }
.c100.p53 { --clock-deg: 190.8; }
.c100.p54 { --clock-deg: 194.4; }
.c100.p55 { --clock-deg: 198; }
.c100.p56 { --clock-deg: 201.6; }
.c100.p57 { --clock-deg: 205.2; }
.c100.p58 { --clock-deg: 208.8; }
.c100.p59 { --clock-deg: 212.4; }
.c100.p60 { --clock-deg: 216; }
.c100.p61 { --clock-deg: 219.6; }
.c100.p62 { --clock-deg: 223.2; }
.c100.p63 { --clock-deg: 226.8; }
.c100.p64 { --clock-deg: 230.4; }
.c100.p65 { --clock-deg: 234; }
.c100.p66 { --clock-deg: 237.6; }
.c100.p67 { --clock-deg: 241.2; }
.c100.p68 { --clock-deg: 244.8; }
.c100.p69 { --clock-deg: 248.4; }
.c100.p70 { --clock-deg: 252; }
.c100.p71 { --clock-deg: 255.6; }
.c100.p72 { --clock-deg: 259.2; }
.c100.p73 { --clock-deg: 262.8; }
.c100.p74 { --clock-deg: 266.4; }
.c100.p75 { --clock-deg: 270; }
.c100.p76 { --clock-deg: 273.6; }
.c100.p77 { --clock-deg: 277.2; }
.c100.p78 { --clock-deg: 280.8; }
.c100.p79 { --clock-deg: 284.4; }
.c100.p80 { --clock-deg: 288; }
.c100.p81 { --clock-deg: 291.6; }
.c100.p82 { --clock-deg: 295.2; }
.c100.p83 { --clock-deg: 298.8; }
.c100.p84 { --clock-deg: 302.4; }
.c100.p85 { --clock-deg: 306; }
.c100.p86 { --clock-deg: 309.6; }
.c100.p87 { --clock-deg: 313.2; }
.c100.p88 { --clock-deg: 316.8; }
.c100.p89 { --clock-deg: 320.4; }
.c100.p90 { --clock-deg: 324; }
.c100.p91 { --clock-deg: 327.6; }
.c100.p92 { --clock-deg: 331.2; }
.c100.p93 { --clock-deg: 334.8; }
.c100.p94 { --clock-deg: 338.4; }
.c100.p95 { --clock-deg: 342; }
.c100.p96 { --clock-deg: 345.6; }
.c100.p97 { --clock-deg: 349.2; }
.c100.p98 { --clock-deg: 352.8; }
.c100.p99 { --clock-deg: 356.4; }
.c100.p100 { --clock-deg: 360; }
/* === INDICATEUR CIRCULAIRE DE TEMPS === */
/* Adaptation pour le thème sombre */
html[class*="-dark"] .c100 {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] .c100::after {
background-color: var(--kr-bg-elevated) !important;
}
html[class*="-dark"] .c100 > span {
color: var(--kr-text-primary) !important;
font-size: 20px !important;
font-weight: 600 !important;
line-height: 80px !important;
width: 80px !important;
height: 80px !important;
}
html[class*="-dark"] .c100 .slice {
border-color: var(--kr-bg-elevated) !important;
}
html[class*="-dark"] .c100 .bar {
border-color: var(--kr-primary) !important;
}
html[class*="-dark"] .c100 .fill {
border-color: var(--kr-bg-elevated) !important;
}
/* Pagination */
html[class*="-dark"] .pagination {
background-color: transparent !important;
}
html[class*="-dark"] .pagination > li > a,
html[class*="-dark"] .pagination > li > span {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] .pagination > li.disabled > a,
html[class*="-dark"] .pagination > li.disabled > span {
background-color: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-muted) !important;
opacity: 0.6 !important;
}
html[class*="-dark"] .pagination > li > a:hover,
html[class*="-dark"] .pagination > li > span:hover {
background-color: var(--kr-bg-hover) !important;
border-color: var(--kr-border-strong) !important;
color: var(--kr-primary) !important;
}
html[class*="-dark"] .pagination > .active > a,
html[class*="-dark"] .pagination > .active > span {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary) !important;
color: var(--kr-text-inverse) !important;
}
html[class*="-dark"] .pagination > .active > a:hover,
html[class*="-dark"] .pagination > .active > span:hover {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary) !important;
color: var(--kr-text-inverse) !important;
}
/* DataTables Pagination */
html[class*="-dark"] .dataTables_paginate {
background-color: transparent !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button {
background-color: var(--kr-bg-elevated) !important;
border: 1px solid var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
background-image: none !important;
box-shadow: none !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button.current {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary) !important;
color: var(--kr-text-inverse) !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button.disabled,
html[class*="-dark"] .dataTables_paginate .paginate_button.disabled:hover {
background-color: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-muted) !important;
opacity: 0.6 !important;
cursor: not-allowed !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button:not(.disabled):hover {
background-color: var(--kr-bg-hover) !important;
border-color: var(--kr-border-strong) !important;
color: var(--kr-primary) !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button.current:hover {
background-color: var(--kr-primary) !important;
border-color: var(--kr-primary) !important;
color: var(--kr-text-inverse) !important;
}
/* DataTables - Wrapper et conteneur */
html[class*="-dark"] .dataTables_wrapper {
color: var(--kr-text-primary);
}
/* DataTables - Info, Filter, Length */
html[class*="-dark"] .dataTables_info,
html[class*="-dark"] .dataTables_filter,
html[class*="-dark"] .dataTables_length {
color: var(--kr-text-secondary) !important;
}
html[class*="-dark"] .dataTables_filter label,
html[class*="-dark"] .dataTables_length label {
color: var(--kr-text-secondary);
}
/* DataTables - Inputs et Selects */
html[class*="-dark"] .dataTables_filter input,
html[class*="-dark"] .dataTables_length select {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] .dataTables_filter input:focus,
html[class*="-dark"] .dataTables_length select:focus {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-primary) !important;
outline: none !important;
}
/* DataTables - Table principale */
html[class*="-dark"] table.dataTable {
background-color: transparent !important;
color: var(--kr-text-primary) !important;
}
/* DataTables - En-têtes */
html[class*="-dark"] table.dataTable thead th,
html[class*="-dark"] table.dataTable thead td {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] table.dataTable thead th.sorting,
html[class*="-dark"] table.dataTable thead th.sorting_asc,
html[class*="-dark"] table.dataTable thead th.sorting_desc {
background-color: var(--kr-bg-elevated) !important;
}
html[class*="-dark"] table.dataTable thead th.sorting:hover,
html[class*="-dark"] table.dataTable thead th.sorting_asc:hover,
html[class*="-dark"] table.dataTable thead th.sorting_desc:hover {
background-color: var(--kr-bg-hover) !important;
}
/* DataTables - Corps de table (lignes) */
html[class*="-dark"] table.dataTable tbody tr {
background-color: var(--kr-bg-surface) !important;
}
html[class*="-dark"] table.dataTable tbody tr.odd {
background-color: var(--kr-bg-surface) !important;
}
html[class*="-dark"] table.dataTable tbody tr.even {
background-color: var(--kr-bg-hover) !important;
}
html[class*="-dark"] table.dataTable tbody tr:hover {
background-color: var(--kr-bg-active) !important;
}
/* DataTables - Cellules */
html[class*="-dark"] table.dataTable tbody td {
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
/* DataTables - Message "Aucune donnée" */
html[class*="-dark"] table.dataTable tbody td.dataTables_empty {
background-color: var(--kr-bg-surface) !important;
color: var(--kr-text-secondary) !important;
}
/* DataTables - Footer */
html[class*="-dark"] table.dataTable tfoot th,
html[class*="-dark"] table.dataTable tfoot td {
background-color: var(--kr-bg-elevated) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
/* ============================================================================
MAP PAGE FIX - Carte au dessus des tableaux
============================================================================ */
/* Donner une hauteur au conteneur de la carte pour que le contenu en dessous
ne se superpose pas. La carte utilise des éléments en position absolue. */
body > div:has([id^="c"]) {
display: block;
position: relative;
min-height: 550px; /* Hauteur de la carte + marge */
}
/* La balise MAP vide ne doit pas prendre de place */
body > map {
display: none;
}
/* ============================================================================
MOBILE RESPONSIVE - Compatibilité mobile (<768px)
Ces styles ne s'appliquent QUE sur mobile et ne modifient PAS le desktop
============================================================================ */
@media (width <= 767px) {
/* ==========================================================================
FIX : Empêcher le scroll automatique vers les ancres au chargement
========================================================================== */
html {
overflow-anchor: none !important; /* Désactive l'ancrage automatique du scroll */
}
/* ==========================================================================
PHASE 1 : CONTAINER RESPONSIVE
Supprime le débordement horizontal causé par la largeur fixe
========================================================================== */
.container {
max-width: 100% !important;
width: 100% !important;
padding-left: 15px !important;
padding-right: 15px !important;
}
/* ==========================================================================
PHASE 2 : NAVBAR MOBILE AMÉLIORÉE
Améliore l'ergonomie du menu hamburger et des icônes
========================================================================== */
/* Fix du layout du header pour que logo et hamburger soient bien alignés */
.navbar-header {
display: flex !important;
justify-content: center !important; /* Logo au centre */
align-items: center !important;
width: 100vw !important; /* Prend toute la largeur du viewport */
position: relative !important; /* Pour positionner le toggle en absolu */
margin-left: calc(-50vw + 50%) !important; /* Centre par rapport au viewport */
margin-right: calc(-50vw + 50%) !important;
padding-left: 0 !important;
padding-right: 0 !important;
height: 60px !important; /* Hauteur fixe pour centrage vertical */
}
.navbar-brand {
float: none !important;
margin: 0 !important; /* Centré par flexbox */
display: flex !important;
align-items: center !important;
padding: 15px !important; /* Ajout d'un padding pour ne pas coller aux bords */
}
/* Boutons plus grands pour touch (minimum 44x44px recommandé) */
.navbar-toggle {
position: absolute !important; /* Position absolue pour le placer en haut à droite */
right: 15px !important; /* Aligné à droite avec espacement */
top: 50% !important;
transform: translateY(-50%) !important; /* Centrage vertical parfait */
padding: 8px !important;
min-width: 48px !important;
min-height: 48px !important;
width: 48px !important;
height: 48px !important;
float: none !important;
margin: 0 !important;
border-radius: 12px !important; /* Coins plus arrondis, style moderne */
border: none !important; /* Pas de bordure pour un style épuré */
background: rgba(255, 255, 255, 0.15) !important;
backdrop-filter: blur(10px) !important; /* Effet de flou moderne */
display: flex !important;
flex-direction: column !important;
justify-content: center !important;
align-items: center !important;
gap: 4px !important; /* Espacement réduit entre les barres */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; /* Animation fluide */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important; /* Ombre légère */
}
.navbar-toggle:hover,
.navbar-toggle:focus {
background: rgba(255, 255, 255, 0.25) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
transform: translateY(-50%) scale(1.05) !important; /* Léger effet de zoom */
}
.navbar-toggle:active {
transform: translateY(-50%) scale(0.98) !important; /* Effet d'appui */
}
/* Barres du hamburger plus stylées et modernes */
.navbar-toggle .icon-bar {
width: 22px !important;
height: 2px !important; /* Plus fines pour un look moderne */
border-radius: 2px !important;
background-color: #fff !important;
display: block !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
/* Animation subtile au hover - Les barres s'écartent légèrement */
.navbar-toggle:hover .icon-bar:nth-child(2) {
width: 18px !important;
margin-left: 4px !important;
}
.navbar-toggle:hover .icon-bar:nth-child(4) {
width: 18px !important;
}
/* Menu déroulant pleine largeur - Respecte le comportement Bootstrap */
.navbar-collapse {
width: 100% !important;
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
}
/* Quand le menu est ouvert (classe .in ajoutée par Bootstrap) */
.navbar-collapse.in {
overflow-y: auto !important;
max-height: calc(100vh - 60px) !important;
}
/* Items de navigation plus espacés */
.navbar-nav > li > a {
padding: 15px 20px !important;
font-size: 16px !important;
}
/* Icônes de la barre (notifications, messages) */
.navbar-nav.navbar-right > li > a {
padding: 15px 12px !important;
}
/* Logo plus petit sur mobile */
.navbar-brand img.kr-logo {
height: 24px !important;
}
/* ==========================================================================
PHASE 3 : PAGE D'ACCUEIL MOBILE
Réorganise les blocs pour une lecture verticale fluide
========================================================================== */
/* Empiler les colonnes verticalement (sauf dans les modales) */
body.mobile-mode > .container > .row > [class*="col-md-"],
body.mobile-mode > .container > .row > [class*="col-sm-"],
body.mobile-mode .container:not(.bootbox) > .row > [class*="col-md-"],
body.mobile-mode .container:not(.bootbox) > .row > [class*="col-sm-"] {
width: 100% !important;
float: none !important;
padding-left: 10px !important;
padding-right: 10px !important;
margin-bottom: 15px !important;
}
/* Carousel responsive */
.carousel {
margin-bottom: 20px !important;
}
.carousel .carousel-caption {
position: relative !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
background: rgba(0, 0, 0, 0.7) !important;
padding: 15px !important;
}
.carousel .carousel-caption h1 {
font-size: 1.5rem !important;
line-height: 1.3 !important;
}
.carousel .carousel-caption p {
font-size: 0.95rem !important;
}
/* Images du carousel */
.carousel-inner > .item > img {
width: 100% !important;
height: auto !important;
}
/* Masquer les statistiques (Membres actifs, Personnages actifs, etc.) en mobile */
a.list-group-item.ds_users,
a.list-group-item.ds_characters,
a.list-group-item.ds_online {
display: none !important;
}
/* N'afficher qu'une seule nouvelle en mode mobile */
.panel-body.panel-news ul.demo li.news-item:nth-child(n+2) {
display: none !important;
}
/* Réduire la taille des flèches de navigation dans la section nouvelles */
.panel-default ul.pagination.pull-right > li > a {
font-size: 12px !important;
padding: 6px 10px !important;
min-width: 32px !important;
min-height: 32px !important;
line-height: 1.2 !important;
}
/* Header "Nouvelles" avec croix à droite */
.list-group-item.active {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
padding: 10px 15px !important;
}
#slide-submenu {
flex-shrink: 0 !important;
margin-left: 10px !important;
cursor: pointer !important;
font-size: 18px !important;
line-height: 1 !important;
padding: 4px 8px !important;
background: transparent !important;
border: none !important;
color: inherit !important;
transition: opacity 0.2s ease !important;
}
#slide-submenu:hover {
opacity: 0.7 !important;
}
/* Masquer le contenu quand replié */
#player-header-section.kr-news-collapsed .panel.panel-default {
display: none !important;
}
/* Stats cards en grille 3 colonnes */
.dashboard-cards-grid {
grid-template-columns: repeat(3, 1fr) !important;
gap: 8px !important;
}
/* Cartes de dashboard plus compactes */
.dashboard-card {
padding: 10px !important;
}
.dashboard-card h4 {
font-size: 1.1rem !important;
}
/* Panels responsive */
.panel {
margin-bottom: 15px !important;
}
.panel-heading {
padding: 10px 15px !important;
display: flex !important;
align-items: center !important;
flex-wrap: wrap !important;
gap: 10px !important;
}
.panel-heading .pull-right {
float: none !important;
margin-left: auto !important;
display: flex !important;
align-items: center !important;
}
.panel-body {
padding: 15px !important;
}
/* Réduire l'espace entre pagination et messages sur les pages de forum */
.pagination {
margin: 10px 0 !important;
}
/* Uniformiser la taille des boutons de pagination */
.pagination > li > a,
.pagination > li > span {
width: 44px !important;
height: 44px !important;
min-width: 44px !important;
min-height: 44px !important;
max-width: 44px !important;
max-height: 44px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
line-height: 1 !important;
box-sizing: border-box !important;
}
/* Réduire l'espace sous le h1 sur les pages de thread */
h1.page-header {
margin-bottom: 10px !important;
}
/* Ancres de messages ne doivent pas prendre d'espace */
a[name^="msg"] {
position: absolute !important;
width: 0 !important;
height: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
/* Wells plus compacts */
.well {
padding: 15px !important;
margin-bottom: 15px !important;
}
/* Alerts responsive */
.alert {
padding: 12px 15px !important;
margin-bottom: 15px !important;
}
/* Mini-chat : masqué par défaut sur mobile, accessible via bouton flottant */
#flap {
position: fixed !important;
top: 0 !important;
right: -100% !important;
width: 85% !important;
max-width: 320px !important;
height: 100vh !important;
z-index: 1050 !important;
transition: right 0.3s ease !important;
overflow-y: auto !important;
background: var(--kr-bg-surface) !important;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3) !important;
}
#flap.mobile-open {
right: 0 !important;
}
/* Bouton MC visible pour ouvrir le chat */
a[href*="#flap"] {
display: flex !important;
position: fixed !important;
bottom: 20px !important;
right: 20px !important;
z-index: 1000 !important;
background: var(--kr-primary) !important;
color: white !important;
border-radius: 50% !important;
width: 56px !important;
height: 56px !important;
align-items: center !important;
justify-content: center !important;
text-align: center !important;
box-shadow: var(--kr-shadow-lg) !important;
font-size: 18px !important;
font-weight: bold !important;
text-decoration: none !important;
}
/* ==========================================================================
PHASE 4 : PAGE PLATEAU DE JEU MOBILE
Réorganise l'interface de jeu pour le mobile
========================================================================== */
/* === PANNEAU DE COMPÉTENCES === */
/* Masquer le panneau latéral, accessible via bouton */
#skills-panel {
position: fixed !important;
top: 0 !important;
left: -100% !important;
width: 85% !important;
max-width: 320px !important;
height: 100vh !important;
z-index: 1050 !important;
transition: left 0.3s ease !important;
overflow-y: auto !important;
background: var(--kr-bg-surface) !important;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.3) !important;
padding: 15px !important;
}
#skills-panel.mobile-open {
left: 0 !important;
}
/* Conteneur parent du skills-panel */
.col-md-1:has(#skills-panel) {
position: static !important;
width: 0 !important;
padding: 0 !important;
margin: 0 !important;
}
/* Grille des compétences plus compacte sur mobile */
#skills-panel .grid-transformed {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 8px !important;
}
/* === PANNEAU PERSONNAGE === */
/* Toutes les colonnes en pleine largeur (page principale uniquement, pas les modales) */
body.mobile-mode > .container > .row > .col-md-3,
body.mobile-mode > .container > .row > .col-md-6,
body.mobile-mode > .container > .row > .col-md-8,
body.mobile-mode > .container > .row > .col-md-1 {
width: 100% !important;
float: none !important;
padding-left: 15px !important;
padding-right: 15px !important;
}
/* Avatar réduit */
.panel-heading img {
max-width: 60px !important;
max-height: 60px !important;
}
/* Stats du personnage */
.panel-body > div {
margin-bottom: 10px !important;
}
/* Barres de progression */
.progress {
height: 24px !important;
margin-bottom: 8px !important;
}
.progress-bar {
font-size: 12px !important;
line-height: 24px !important;
}
/* === CARTE DU JEU === */
/* Carte avec scroll horizontal si nécessaire */
.panel-body:has(map),
.panel-body:has([id^="c"]) {
overflow-x: auto !important;
-webkit-overflow-scrolling: touch !important;
padding: 10px !important;
}
/* Conteneur de carte */
body > div:has([id^="c"]) {
min-height: 400px !important;
}
/* Images de la carte */
map + img,
[id^="c"] img {
max-width: none !important;
height: auto !important;
}
/* === LISTE DES PERSONNAGES === */
/* En liste verticale compacte */
.list-group {
margin-bottom: 15px !important;
}
.list-group-item {
padding: 10px 12px !important;
min-height: 44px !important;
justify-content: space-between !important;
display: flex !important;
align-items: center !important;
gap: 10px !important;
}
/* Avatars de la liste réduits */
.list-group-item img {
width: 40px !important;
height: 40px !important;
flex-shrink: 0 !important;
}
/* Texte des items */
.list-group-item-heading {
font-size: 14px !important;
margin-bottom: 4px !important;
}
.list-group-item-text {
font-size: 12px !important;
}
/* === PANNEAU COMMERCE === */
/* Items du commerce en liste compacte */
.panel-body h4 {
font-size: 1rem !important;
margin-top: 15px !important;
margin-bottom: 10px !important;
}
/* Items de commerce */
.panel-body a {
display: flex !important;
align-items: center !important;
padding: 8px !important;
gap: 10px !important;
font-size: 14px !important;
margin-bottom: 5px !important;
border-radius: 4px !important;
}
.panel-body a:hover {
background-color: var(--kr-bg-hover) !important;
}
.panel-body a img {
width: 32px !important;
height: 32px !important;
flex-shrink: 0 !important;
}
/* Tables responsive */
.table-responsive {
overflow-x: auto !important;
-webkit-overflow-scrolling: touch !important;
margin-bottom: 15px !important;
}
/* Tables plus compactes */
.table > thead > tr > th,
.table > tbody > tr > th,
.table > tfoot > tr > th,
.table > thead > tr > td,
.table > tbody > tr > td,
.table > tfoot > tr > td {
padding: 8px !important;
font-size: 13px !important;
}
/* ==========================================================================
PHASE 5 : ÉLÉMENTS TOUCH-FRIENDLY
Assure que tous les éléments interactifs sont cliquables facilement
========================================================================== */
/* Taille minimale des zones cliquables (44x44px minimum recommandé) */
a:not(.list-group-item),
button,
.btn,
input[type="button"],
input[type="submit"],
input[type="reset"] {
min-height: 44px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Boutons de formulaire */
.btn {
padding: 12px 20px !important;
font-size: 16px !important;
}
.btn-xs {
min-height: 32px !important;
padding: 6px 12px !important;
font-size: 14px !important;
}
.btn-sm {
min-height: 38px !important;
padding: 8px 16px !important;
font-size: 14px !important;
}
.btn-lg {
min-height: 50px !important;
padding: 14px 24px !important;
font-size: 18px !important;
}
/* Formulaires */
.form-control {
height: 44px !important;
font-size: 16px !important; /* Évite le zoom iOS */
padding: 10px 12px !important;
}
textarea.form-control {
height: auto !important;
min-height: 100px !important;
}
select.form-control {
height: 44px !important;
}
/* Checkboxes et radios plus grands */
input[type="checkbox"],
input[type="radio"] {
width: 20px !important;
height: 20px !important;
margin: 4px !important;
}
/* Labels cliquables */
label {
display: inline-block !important;
padding: 5px 0 !important;
cursor: pointer !important;
}
/* Espacement entre éléments cliquables */
.btn + .btn {
margin-top: 8px !important;
margin-left: 0 !important;
}
.btn-group-vertical > .btn {
margin-top: 0 !important;
margin-bottom: 8px !important;
}
/* Boutons groupés en colonne sur mobile */
.btn-group {
display: flex !important;
flex-direction: column !important;
width: 100% !important;
}
.btn-group > .btn {
width: 100% !important;
margin-bottom: 8px !important;
}
/* Pagination plus grande */
.pagination > li > a,
.pagination > li > span {
padding: 12px 16px !important;
font-size: 16px !important;
}
/* Breadcrumb responsive */
.breadcrumb {
padding: 10px 15px !important;
font-size: 14px !important;
}
/* Tabs plus grandes */
.nav-tabs > li > a {
padding: 12px 16px !important;
font-size: 16px !important;
}
/* Dropdown menu */
.dropdown-menu > li > a {
padding: 12px 20px !important;
font-size: 16px !important;
}
/* Modal responsive */
.modal-dialog {
width: auto !important;
margin: 10px !important;
}
.modal-content {
border-radius: 8px !important;
}
.modal-header {
padding: 15px !important;
}
.modal-body {
padding: 15px !important;
}
.modal-footer {
padding: 15px !important;
}
/* Popover et tooltip responsive */
.popover {
max-width: calc(100vw - 40px) !important;
}
.tooltip {
font-size: 14px !important;
}
/* Footer responsive */
.footer {
padding: 15px !important;
font-size: 14px !important;
}
/* Typographie mobile */
h1 { font-size: 1.75rem !important; }
h2 { font-size: 1.5rem !important; }
h3 { font-size: 1.25rem !important; }
h4 { font-size: 1.1rem !important; }
h5 { font-size: 1rem !important; }
h6 { font-size: 0.9rem !important; }
/* Espacement vertical */
.row {
margin-left: -10px !important;
margin-right: -10px !important;
}
/* Masquer les éléments non essentiels sur mobile */
.hidden-xs {
display: none !important;
}
/* Afficher les éléments mobile uniquement */
.visible-xs,
.visible-xs-block,
.visible-xs-inline,
.visible-xs-inline-block {
display: block !important;
}
.visible-xs-inline {
display: inline !important;
}
.visible-xs-inline-block {
display: inline-block !important;
}
}
/* ==========================================================================
OVERLAY MOBILE - Pour fermer les panneaux latéraux
========================================================================== */
.kr-mobile-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1040;
opacity: 0;
transition: opacity 0.3s ease;
}
.kr-mobile-overlay.active {
display: block;
opacity: 1;
}
/* Bouton de fermeture dans les panneaux mobiles */
.kr-mobile-close {
position: absolute;
top: 10px;
right: 10px;
width: 40px;
height: 40px;
background: var(--kr-primary);
color: white;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
font-size: 20px;
}
.kr-mobile-close:hover {
background: var(--kr-primary-dark);
}
/* Bouton pour ouvrir le panneau de compétences sur mobile */
.kr-mobile-skills-toggle {
position: fixed !important;
bottom: 90px !important;
right: 20px !important;
z-index: 1000 !important;
width: 56px !important;
height: 56px !important;
border-radius: 50% !important;
padding: 0 !important;
display: none !important;
box-shadow: var(--kr-shadow-lg) !important;
}
@media (width <= 767px) {
.kr-mobile-skills-toggle {
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
}
/* ============================================
TASK-1.1 - BREAKPOINTS (Bootstrap 3)
============================================ */
/* Bootstrap 3 utilise ces breakpoints précis :
- xs: < 768px (mobile)
- sm: >= 768px (tablet)
- md: >= 992px (desktop)
- lg: >= 1200px (large desktop)
*/
/* Mobile First approach */
/* Default styles = mobile (< 768px) */
/* Small devices (tablets, >= 768px) */
@media (width >= 768px) {
/* Retour progressif au layout desktop */
body.mobile-mode {
/* Désactiver progressivement les adaptations mobiles */
}
}
/* Medium devices (desktops, >= 992px) */
@media (width >= 992px) {
/* Layout desktop complet */
body.mobile-mode {
/* Annuler complètement les adaptations mobiles */
}
}
/* Large devices (large desktops, >= 1200px) */
@media (width >= 1200px) {
/* Layout large desktop si nécessaire */
}
/* ============================================
TASK-1.1 - CLASSES UTILITAIRES MOBILE
============================================ */
/* Masquer certains éléments en mode mobile */
@media (width <= 767px) {
body.mobile-mode .desktop-only,
body.mobile-mode .mobile-hidden {
display: none;
}
}
/* Masquer certains éléments en mode desktop */
@media (width >= 768px) {
body.mobile-mode .mobile-only {
display: none;
}
}
/* Classes utilitaires supplémentaires compatibles BS3 */
@media (width < 768px) {
.visible-xs-block { display: block; }
.visible-xs-inline { display: inline; }
.visible-xs-inline-block { display: inline-block; }
.hidden-xs { display: none; }
}
/* ============================================
TASK-1.4 - MINI-PROFIL COLLAPSIBLE
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Container du mini-profil */
.mobile-mini-profile {
position: relative;
background: var(--kr-bg-surface) !important;
border-bottom: 1px solid var(--kr-border-default) !important;
padding: 12px;
cursor: pointer;
transition: all var(--transition-normal);
}
.mobile-mini-profile:active {
background: var(--kr-bg-hover) !important;
}
/* État replié (défaut) */
.mobile-mini-profile.collapsed {
min-height: 72px;
}
/* État déplié */
.mobile-mini-profile.expanded {
min-height: 200px;
}
/* Header toujours visible */
.mobile-mini-profile-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
/* Avatar */
.mobile-mini-profile .avatar {
width: 48px !important;
height: 48px !important;
border-radius: 4px;
flex-shrink: 0;
transition: all var(--transition-normal);
object-fit: cover;
}
.mobile-mini-profile.expanded .avatar {
width: 64px !important;
height: 64px !important;
}
/* Info principale */
.mobile-mini-profile-info {
flex: 1;
min-width: 0;
}
.mobile-mini-profile-name-row {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 8px !important;
margin-bottom: 2px !important;
}
.mobile-mini-profile-name {
font-weight: 600 !important;
font-size: 14px !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
color: var(--kr-text-primary) !important;
flex: 1 !important;
}
.mobile-mini-profile-manage-btn {
min-width: 44px !important;
min-height: 44px !important;
padding: 8px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-size: 20px !important;
flex-shrink: 0 !important;
border: none !important;
background: var(--kr-bg-surface) !important;
}
.mobile-mini-profile-manage-btn:active {
background: var(--kr-bg-hover) !important;
}
.mobile-mini-profile-money {
display: flex !important;
align-items: center !important;
gap: 4px !important;
font-size: 13px !important;
color: var(--kr-text-secondary) !important;
}
.mobile-mini-profile-clock {
font-size: 12px !important;
color: var(--kr-text-muted) !important;
margin-left: 8px !important;
}
/* Bouton settings */
.mobile-mini-profile-settings {
position: absolute !important;
top: 12px !important;
right: 12px !important;
width: 32px !important;
height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
background: var(--kr-bg-hover) !important;
border-radius: 50% !important;
cursor: pointer !important;
font-size: 16px !important;
z-index: 10 !important;
text-decoration: none !important;
}
.mobile-mini-profile-settings:hover {
background: var(--kr-bg-active) !important;
}
/* Jauges compactes (toujours visibles) */
.mobile-mini-profile-gauges-compact {
display: flex !important;
gap: 8px !important;
font-size: 11px !important;
margin-top: 4px !important;
}
.mobile-gauge-compact {
flex: 1 !important;
display: flex !important;
align-items: center !important;
gap: 2px !important;
}
.mobile-gauge-compact-label {
font-weight: 600 !important;
min-width: 20px !important;
font-size: 10px !important;
}
.mobile-gauge-compact-bar {
flex: 1 !important;
height: 6px !important;
background: var(--kr-border-default) !important;
border-radius: 3px !important;
overflow: hidden !important;
position: relative !important;
}
.mobile-gauge-compact-fill {
position: absolute !important;
left: 0 !important;
top: 0 !important;
height: 100% !important;
transition: width var(--transition-normal) !important;
}
.mobile-gauge-compact-fill.pv {
background: var(--kr-gauge-pv) !important;
}
.mobile-gauge-compact-fill.pm {
background: var(--kr-gauge-pm) !important;
}
.mobile-gauge-compact-fill.pp {
background: var(--kr-gauge-pp) !important;
}
.mobile-gauge-compact-value {
font-size: 10px !important;
color: var(--kr-text-secondary) !important;
white-space: nowrap !important;
min-width: 20px !important;
text-align: right !important;
}
/* Détails (visibles uniquement si déplié) */
.mobile-mini-profile-details {
max-height: 0 !important;
overflow: hidden !important;
transition: max-height var(--transition-normal) !important;
}
.mobile-mini-profile.expanded .mobile-mini-profile-details {
max-height: 1200px !important;
}
/* Jauges détaillées */
.mobile-mini-profile-gauges-full {
margin: 12px 0 !important;
padding: 12px !important;
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: 4px !important;
}
.mobile-gauge-full {
margin-bottom: 8px !important;
}
.mobile-gauge-full:last-child {
margin-bottom: 0 !important;
}
.mobile-gauge-full-header {
display: flex !important;
justify-content: space-between !important;
font-size: 12px !important;
font-weight: 600 !important;
margin-bottom: 4px !important;
}
.mobile-gauge-full-bar {
height: 12px !important;
background: var(--kr-border-default) !important;
border-radius: 6px !important;
overflow: hidden !important;
position: relative !important;
}
.mobile-gauge-full-fill {
position: absolute !important;
left: 0 !important;
top: 0 !important;
height: 100% !important;
transition: width var(--transition-normal) !important;
}
.mobile-gauge-full-fill.pv {
background: var(--kr-gauge-pv) !important;
}
.mobile-gauge-full-fill.pm {
background: var(--kr-gauge-pm) !important;
}
.mobile-gauge-full-fill.pp {
background: var(--kr-gauge-pp) !important;
}
/* Caractéristiques */
.mobile-mini-profile-characteristics {
display: grid !important;
grid-template-columns: repeat(6, 1fr) !important;
gap: 0 !important;
margin: 12px 0 !important;
padding: 12px !important;
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: 4px !important;
}
.mobile-characteristic-badge {
min-width: 44px !important;
min-height: 44px !important;
margin: 4px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.mobile-characteristic-badge:active {
background: var(--kr-bg-hover) !important;
}
/* Compétences */
.mobile-mini-profile-skills {
max-height: 350px !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
margin: 12px 0 !important;
padding: 12px !important;
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: 4px !important;
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)) !important;
gap: 8px !important;
}
.mobile-skill-item {
min-height: 44px !important;
margin: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.mobile-skill-item:active {
background: var(--kr-bg-hover) !important;
}
/* Masquer le profil original en mobile */
#player-header-section {
display: none !important;
}
/* Masquer la section actions originale en mobile */
#player-actions-section {
display: none !important;
}
}
}
/* ============================================
TASK-1.6 - MASQUER 18 COMPÉTENCES
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Masquer le panel des 18 compétences */
/* Les compétences sont accessibles via l'onglet "Personnage" (/jouer/perso) */
.panel-body.grid-transformed {
display: none !important;
}
/* Masquer le bouton "Afficher les compétences" */
.kr-mobile-skills-toggle {
display: none !important;
}
/* Masquer l'overlay des compétences si présent */
.kr-mobile-overlay {
display: none !important;
}
}
}
/* ============================================
TASK-1.5 - ACTIONS RAPIDES (Bootstrap 3)
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Container actions - Utilise btn-group-justified BS3 */
.mobile-quick-actions.btn-group-justified {
display: flex !important;
flex-direction: row !important;
width: 100% !important;
border-bottom: 1px solid var(--kr-border-default) !important;
background: var(--kr-bg-surface) !important;
}
/* Wrapper btn-group requis par BS3 justified */
.mobile-quick-actions .btn-group {
flex: 1 1 0 !important;
display: flex !important;
}
/* Bouton d'action individuel - Réutilise btn BS3 */
.mobile-quick-action.btn {
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
flex: 1 !important;
width: 100% !important;
min-height: 60px !important;
padding: 8px 4px !important;
background: var(--kr-bg-surface) !important;
color: var(--kr-text-primary) !important;
border-radius: 0 !important;
border-left: none !important;
border-right: 1px solid var(--kr-border-default) !important;
border-top: none !important;
border-bottom: none !important;
transition: all var(--transition-fast) !important;
}
.mobile-quick-action.btn:first-child {
border-left: 1px solid var(--kr-border-default) !important;
}
.mobile-quick-action.btn:last-child {
border-right: 1px solid var(--kr-border-default) !important;
}
.mobile-quick-action.btn:active {
box-shadow: inset 0 3px 5px var(--kr-overlay-dark-125) !important;
transform: scale(0.95) !important;
}
.mobile-quick-action.btn.disabled,
.mobile-quick-action.btn[disabled] {
opacity: 0.65 !important;
cursor: not-allowed !important;
}
/* Icône */
.mobile-quick-action-icon {
font-size: 20px !important;
margin-bottom: 2px !important;
display: block !important;
}
.mobile-quick-action.btn i {
font-size: 20px !important;
margin-bottom: 2px !important;
display: block !important;
}
/* Label */
.mobile-quick-action-label {
font-size: 11px !important;
display: block !important;
line-height: 1.2 !important;
margin-top: 2px !important;
}
}
}
/* ============================================
TASK-1.3 - TAB BAR NAVIGATION (Bootstrap 3)
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Tab bar container - Utilise nav-tabs BS3 */
.mobile-tab-bar.nav-tabs {
position: sticky;
top: var(--mobile-header-height);
z-index: calc(var(--z-header) - 1);
background: var(--kr-bg-surface) !important;
border-bottom: 2px solid var(--kr-border-default) !important;
margin-bottom: 0;
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
/* Masquer scrollbar */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
.mobile-tab-bar.nav-tabs::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
/* Tabs individuels - Utilise les styles BS3 */
.mobile-tab-bar.nav-tabs > li {
flex: 1 1 0; /* Répartition égale */
float: none;
min-width: 0; /* Permet le shrink */
}
.mobile-tab-bar.nav-tabs > li > a {
min-height: var(--mobile-touch-target);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px 4px;
margin-right: 0;
border-radius: 0;
border: none;
border-bottom: 3px solid transparent;
transition: all var(--transition-fast);
color: var(--kr-text-primary) !important;
background: transparent !important;
}
/* Icône de tab */
.mobile-tab-icon {
font-size: 18px !important;
margin-bottom: 4px !important;
display: block !important;
}
/* Label de tab */
.mobile-tab-label {
font-size: 10px !important;
display: block !important;
line-height: 1.2 !important;
text-align: center !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: 100% !important;
}
.mobile-tab-bar.nav-tabs > li > a:hover {
background: var(--kr-bg-hover) !important;
border-color: transparent;
}
.mobile-tab-bar.nav-tabs > li.active > a,
.mobile-tab-bar.nav-tabs > li.active > a:hover,
.mobile-tab-bar.nav-tabs > li.active > a:focus {
color: var(--kr-primary);
background: transparent;
border-bottom-color: var(--kr-primary);
font-weight: 600;
}
/* Badge notifications (utilise badge BS3) */
.mobile-tab-bar.nav-tabs .badge {
margin-left: var(--mobile-spacing-xs);
}
/* Indicateur de scroll (optionnel) */
.mobile-tab-bar.nav-tabs::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 20px;
background: linear-gradient(to right, transparent, var(--kr-bg-surface));
pointer-events: none;
}
.mobile-tab-bar.nav-tabs.scrolled-end::after {
display: none;
}
}
}
/* ============================================
TASK-1.2 - HEADER RESPONSIVE (Bootstrap 3)
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Header principal - Utilise les classes BS3 */
.navbar,
.navbar-default,
.navbar-inverse {
position: sticky;
top: 0;
z-index: var(--z-header);
background: var(--kr-navbar-bg);
border-bottom: 1px solid var(--kr-overlay-light-10);
box-shadow: 0 2px 4px var(--kr-overlay-dark-30);
margin-bottom: 0;
min-height: var(--mobile-header-height);
}
.navbar .container {
padding-left: 10px;
padding-right: 10px;
}
/* Zone gauche - Hamburger (réutilise navbar-toggle BS3) */
.navbar-toggle {
display: block;
margin: 8px 10px;
padding: 9px 10px;
border: 1px solid var(--kr-overlay-light-20);
order: 1;
margin-right: 0;
margin-left: auto;
border-radius: 4px;
background: transparent;
}
.navbar-toggle:hover,
.navbar-toggle:focus {
background: var(--kr-overlay-light-10);
}
.navbar-toggle .icon-bar {
background-color: var(--kr-white);
}
/* Logo visible dans la navbar à gauche */
.navbar-header .navbar-brand {
display: flex !important;
align-items: center;
padding: 8px 12px !important;
margin-right: auto !important;
height: var(--mobile-header-height);
min-width: 44px; /* Zone tactile minimum WCAG 2.5.5 */
}
.navbar-header .navbar-brand img {
max-height: 32px;
width: auto;
}
/* Feedback tactile */
.navbar-header .navbar-brand:active {
background: var(--kr-overlay-dark-20);
transform: scale(0.95);
}
/* Cacher le logo déplacé sous la navbar */
.navbar-brand[data-moved-below-navbar] {
display: none !important;
}
/* Boutons déplacés dans le header */
.navbar-header-buttons-left,
.navbar-header-buttons-right {
display: flex;
align-items: center;
height: var(--mobile-header-height);
}
.navbar-header-buttons-left {
order: -1; /* Place à gauche avant le toggle */
padding-left: 8px;
}
.navbar-header-buttons-right {
order: 2; /* Avant le logo */
margin-left: auto; /* Pousse à droite */
padding-right: 8px;
}
.navbar-header-buttons-left li,
.navbar-header-buttons-right li {
list-style: none;
display: inline-block;
margin: 0 2px;
}
.navbar-header-buttons-left li a,
.navbar-header-buttons-right li a {
display: flex;
align-items: center;
justify-content: center;
min-width: 44px !important;
min-height: 44px !important;
padding: 10px !important;
font-size: 18px;
color: var(--kr-white) !important;
text-decoration: none;
border-radius: 4px;
transition: background-color 150ms ease;
position: relative;
}
.navbar-header-buttons-left li a:active,
.navbar-header-buttons-right li a:active {
background-color: var(--kr-overlay-light-10);
}
/* Badge dans le header */
.navbar-header-buttons-left .badge,
.navbar-header-buttons-right .badge {
position: absolute !important;
top: 6px !important;
right: 6px !important;
min-width: 16px !important;
height: 16px !important;
padding: 2px 4px !important;
font-size: 10px !important;
line-height: 12px !important;
border-radius: 8px !important;
}
/* Réorganiser l'ordre des éléments du header */
.navbar-header {
display: flex;
align-items: center;
width: 100%;
}
/* Zone droite - Icônes (conservées telles quelles) */
.navbar-right {
margin: 0 !important;
padding: 8px 0 !important;
border-top: 1px solid var(--kr-overlay-light-10) !important;
}
.navbar-right > li > a {
padding: 15px 10px;
}
/* Menu mobile - Utilise navbar-collapse BS3 */
.navbar-collapse {
border-top: 1px solid var(--kr-overlay-light-10);
box-shadow: inset 0 1px 0 var(--kr-overlay-light-05);
max-height: calc(100vh - var(--mobile-header-height));
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.navbar-collapse.in,
.navbar-collapse.collapsing {
overflow-y: auto;
height: auto !important; /* Force hauteur auto pour éviter les problèmes de collapse.js */
}
/* Navigation dans le collapse */
.navbar-nav {
margin: 0;
padding: 8px 0;
}
/* Liens principaux du menu - optimisés pour le tactile */
.navbar-nav > li > a {
min-height: 44px !important; /* WCAG 2.5.5 - zone tactile minimum */
padding: 14px 20px !important;
font-size: 16px !important; /* Évite le zoom automatique iOS */
line-height: 1.5 !important;
display: flex !important;
align-items: center !important;
border-bottom: 1px solid var(--kr-overlay-light-05) !important;
transition: background-color 150ms ease !important;
}
/* Feedback tactile au toucher */
.navbar-nav > li > a:active {
background: var(--kr-overlay-light-10) !important;
transition: none !important;
}
/* Masquer les carets (chevrons) en mobile - navigation directe */
.navbar-nav > li > a .caret {
display: none !important;
}
/* Dropdowns dans le menu mobile - masqués par défaut car navigation directe */
.navbar-nav .dropdown-menu {
display: none !important; /* Masquer les sous-menus en mobile */
}
.navbar-right > li {
display: inline-block !important;
}
.navbar-right > li > a,
.navbar-right > li > button {
min-width: 44px !important; /* Zone tactile minimum */
min-height: 44px !important;
padding: 12px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
font-size: 18px !important; /* Icônes plus grandes */
border-radius: 4px !important;
margin: 0 4px !important;
}
.navbar-right > li > a:active,
.navbar-right > li > button:active {
background: var(--kr-overlay-light-10) !important;
}
/* Badge de notification */
.navbar-right .badge {
position: absolute !important;
top: 8px !important;
right: 8px !important;
min-width: 18px !important;
height: 18px !important;
padding: 2px 5px !important;
font-size: 11px !important;
line-height: 14px !important;
}
}
}
/* ============================================
TASK-2.2 - ACCORDÉON GROUPES (Base)
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Header de groupe avec accordéon */
.dashboard-section-header-accordion {
position: relative;
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
transition: background-color var(--transition-fast);
}
.dashboard-section-header-accordion:active {
background-color: var(--kr-overlay-light-05);
}
/* Icône chevron pour l'accordéon */
.dashboard-section-header-accordion .accordion-icon {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%) rotate(0deg);
transition: transform var(--transition-normal);
color: var(--kr-text-secondary);
font-size: 14px;
pointer-events: none;
}
/* Rotation de l'icône quand le groupe est ouvert */
.dashboard-section-header-accordion.expanded .accordion-icon {
transform: translateY(-50%) rotate(-180deg);
}
/* Grille de membres collapsible */
.dashboard-cards-grid {
overflow: hidden;
transition: max-height var(--transition-normal) ease-in-out,
opacity var(--transition-normal) ease-in-out,
margin var(--transition-fast);
max-height: 5000px; /* Grande valeur pour le contenu étendu */
opacity: 1;
}
/* État collapsed */
.dashboard-cards-grid.collapsed {
max-height: 0;
opacity: 0;
margin: 0;
pointer-events: none;
}
/* Espacement pour les groupes */
.dashboard-section {
margin-bottom: 10px;
}
/* Mon groupe - style spécial */
.dashboard-section-mygroup .dashboard-section-header {
background: var(--kr-bg-hover);
border-left: 3px solid var(--kr-primary);
}
}
}
/* ============================================
TASK-2.3 - CARDS PERSOS (Mobile Optimized)
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Grille de cartes compacte */
.dashboard-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 8px;
padding: 10px;
background: var(--kr-bg-surface);
}
/* Carte de personnage compacte */
.dashboard-card {
position: relative;
width: 100%;
min-height: 140px;
margin: 0;
}
/* Lien principal de la carte */
.dashboard-card-link {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px 4px;
background: var(--kr-bg-hover);
border: 1px solid var(--kr-border-default);
border-radius: 8px;
text-decoration: none;
transition: all var(--transition-fast);
}
.dashboard-card-link:hover,
.dashboard-card-link:active {
background: var(--kr-bg-active);
border-color: var(--kr-primary);
transform: translateY(-1px);
}
/* Header avec avatar */
.dashboard-card-header {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
margin-bottom: 6px;
}
/* Wrapper d'avatar avec cercle HP */
.dashboard-card-avatar-wrapper {
position: relative;
width: 60px;
height: 60px;
margin-bottom: 4px;
}
/* Cercle de vie (HP) */
.dashboard-card-hp-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px !important;
height: 60px !important;
}
.dashboard-card-hp-circle circle {
stroke-width: 2 !important;
}
/* Avatar du personnage */
.dashboard-card-avatar {
position: relative;
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
margin: 5px;
z-index: 1;
}
/* Container du nom */
.dashboard-card-name-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
gap: 2px;
}
/* Icône du monde */
.dashboard-card-world {
width: 14px;
height: 14px;
opacity: 0.6;
}
/* Nom du personnage */
.dashboard-card-name {
font-size: 11px;
font-weight: 500;
color: var(--kr-text-primary);
text-align: center;
line-height: 1.2;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
}
/* Body avec statut */
.dashboard-card-body {
width: 100%;
padding: 0 2px;
}
/* Statut du personnage */
.dashboard-card-status {
font-size: 9px;
color: var(--kr-text-secondary);
text-align: center;
font-style: italic;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Badge PNJ */
.dashboard-card-badge {
position: absolute;
top: 4px;
right: 4px;
background: var(--kr-primary);
color: var(--kr-text-primary);
font-size: 8px;
font-weight: 600;
padding: 2px 4px;
border-radius: 4px;
text-transform: uppercase;
z-index: 2;
}
/* Actions (profil, message) */
.dashboard-card-actions {
display: flex;
gap: 8px;
justify-content: center;
padding: 6px 4px 4px;
border-top: 1px solid var(--kr-border-default);
margin-top: 6px;
}
.dashboard-card-actions a {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
background: var(--kr-bg-surface);
border: 1px solid var(--kr-border-default);
border-radius: 6px;
color: var(--kr-text-secondary);
transition: all var(--transition-fast);
text-decoration: none;
}
.dashboard-card-actions a:hover {
background: var(--kr-primary);
border-color: var(--kr-primary);
color: var(--kr-text-primary);
transform: scale(1.1);
}
.dashboard-card-actions i {
font-size: 13px;
}
}
}
/* ============================================================================
* TASK-2.5 : Commerce - Accordéon catégories
* ============================================================================
*/
/* Header de catégorie cliquable */
body.mobile-mode .commerce-category-header {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
position: relative;
transition: background-color var(--transition-fast);
}
body.mobile-mode .commerce-category-header:hover {
background-color: var(--kr-bg-hover);
}
body.mobile-mode .commerce-category-header:active {
background-color: var(--kr-bg-active);
}
/* Icône chevron */
body.mobile-mode .commerce-category-header .accordion-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
transition: transform var(--transition-normal);
font-size: 14px;
color: var(--kr-text-secondary);
}
body.mobile-mode .commerce-category-header.expanded .accordion-icon {
transform: translateY(-50%) rotate(-180deg);
}
/* Conteneur de produits collapsible */
body.mobile-mode .commerce-products-container {
max-height: 5000px;
opacity: 1;
overflow: hidden;
transition: max-height var(--transition-normal), opacity var(--transition-normal);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 8px;
padding: 8px 0;
}
body.mobile-mode .commerce-products-container.collapsed {
max-height: 0 !important;
opacity: 0;
}
/* ============================================================================
* TASK-2.6 : Commerce - Cards produits compactes
* ============================================================================
*/
/* Carte produit compacte */
body.mobile-mode .commerce-products-container > a.list-group-item {
display: flex !important;
flex-direction: column;
padding: 8px !important;
min-height: 120px;
border-radius: 8px;
background-color: var(--kr-bg-surface);
border: 1px solid var(--kr-border-default);
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
body.mobile-mode .commerce-products-container > a.list-group-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* Prix en haut à droite */
body.mobile-mode .commerce-products-container > a.list-group-item .mention {
position: absolute;
top: 4px;
right: 4px;
float: none !important;
font-size: 10px;
line-height: 1.2;
text-align: right;
background-color: rgba(0, 0, 0, 0.6);
padding: 2px 4px;
border-radius: 4px;
}
body.mobile-mode .commerce-products-container > a.list-group-item .mention .xmini {
font-size: 9px;
opacity: 0.8;
}
/* Image centrée */
body.mobile-mode .commerce-products-container > a.list-group-item img {
float: none !important;
display: block;
margin: 0 auto 8px;
width: 40px;
height: 40px;
}
/* Titre du produit */
body.mobile-mode .commerce-products-container > a.list-group-item h4 {
font-size: 11px;
line-height: 1.3;
margin: 0 0 4px;
text-align: center;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
/* Description (optionnel, peut être masquée sur mobile) */
body.mobile-mode .commerce-products-container > a.list-group-item p {
font-size: 9px;
line-height: 1.2;
margin: 0;
text-align: center;
color: var(--kr-text-secondary);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* Masquer l'icône carré de la description */
body.mobile-mode .commerce-products-container > a.list-group-item p i {
display: none;
}
/* ============================================================================
* TASK-2.4 : Section bâtiment collapsible
* ============================================================================
*/
/* Header de section bâtiment cliquable */
body.mobile-mode .building-section-header {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
position: relative;
transition: background-color var(--transition-fast);
}
body.mobile-mode .building-section-header:hover {
background-color: var(--kr-bg-hover);
}
body.mobile-mode .building-section-header:active {
background-color: var(--kr-bg-active);
}
/* Icône chevron */
body.mobile-mode .building-section-header .accordion-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
transition: transform var(--transition-normal);
font-size: 14px;
color: var(--kr-text-secondary);
}
body.mobile-mode .building-section-header.expanded .accordion-icon {
transform: translateY(-50%) rotate(-180deg);
}
/* Contenu collapsible */
body.mobile-mode .building-section-content {
max-height: 5000px;
opacity: 1;
overflow: hidden;
transition: max-height var(--transition-normal), opacity var(--transition-normal);
}
body.mobile-mode .building-section-content.collapsed {
max-height: 0 !important;
opacity: 0;
}
/* ============================================================================
* TASK-2.1 : Accès aux pièces en ligne horizontale avec croix directionnelle
* Style identique aux actions rapides (Dormir, Prier, etc.)
* ============================================================================ */
/* Conteneur principal : table layout comme mobile-quick-actions */
body.mobile-mode .kr-navigation-row {
display: table !important;
width: 100%;
table-layout: fixed;
margin-bottom: 5px;
border-collapse: separate;
border-spacing: 0;
}
/* Réduire l'espacement du container suivant */
body.mobile-mode .kr-navigation-row + .container {
padding-top: 10px !important;
}
/* Groupes (croix + cartes) - affichage en cellules de table */
body.mobile-mode .kr-navigation-row > .btn-group {
display: table-cell !important;
width: 1%;
vertical-align: middle;
float: none !important;
}
/* Croix directionnelle - même style que les actions rapides */
body.mobile-mode .kr-direction-link {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 60px !important;
padding: 0 !important;
text-align: center;
position: relative;
}
body.mobile-mode .kr-direction-link img {
width: 60px;
height: 60px;
display: block;
}
body.mobile-mode .kr-direction-link map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Cases des accès aux pièces - même style que mobile-quick-action */
body.mobile-mode .kr-room-link {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 60px !important;
padding: 0 !important;
text-align: center;
font-size: 10px;
line-height: 1.2;
}
body.mobile-mode .kr-room-link img {
width: 60px;
height: 60px;
display: block;
}
/* Premier élément : bordures arrondies à gauche */
body.mobile-mode .kr-navigation-row > .btn-group:first-child .kr-direction-link,
body.mobile-mode .kr-navigation-row > .btn-group:first-child .kr-room-link {
border-radius: 4px 0 0 4px;
}
/* Dernier élément : bordures arrondies à droite */
body.mobile-mode .kr-navigation-row > .btn-group:last-child .kr-room-link {
border-radius: 0 4px 4px 0;
}
/* Éléments du milieu : pas de bordure gauche pour éviter le doublon */
body.mobile-mode .kr-navigation-row > .btn-group:not(:first-child) .kr-direction-link,
body.mobile-mode .kr-navigation-row > .btn-group:not(:first-child) .kr-room-link {
border-left: none;
}
/* Si un seul élément : bordures arrondies partout */
body.mobile-mode .kr-navigation-row > .btn-group:only-child .kr-direction-link,
body.mobile-mode .kr-navigation-row > .btn-group:only-child .kr-room-link {
border-radius: 4px;
}
/* ============================================
GROUPES DE PERSONNAGES - STYLE BARRE
============================================ */
@media (width <= 767px) {
body.mobile-mode {
/* Mode mobile activé */
}
/* Retrait padding conteneurs et colonnes Bootstrap en mobile */
body.mobile-mode .container,
body.mobile-mode .container-fluid,
body.mobile-mode [class*="col-"] {
padding-left: 0 !important;
padding-right: 0 !important;
}
body.mobile-mode .row {
margin-left: 0 !important;
margin-right: 0 !important;
}
/* Conteneur dashboard pleine largeur */
body.mobile-mode .dashboard {
margin-left: 0 !important;
margin-right: 0 !important;
width: 100% !important;
padding: 0 !important;
}
/* Style du panel-group pour ressembler aux barres du dessus */
.panel-group {
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-left: none !important;
border-right: none !important;
border-radius: 0 !important;
margin-bottom: 5px !important;
margin-left: -15px !important;
margin-right: -15px !important;
width: calc(100% + 30px) !important;
}
/* Panel individuel */
.panel-group .panel {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
margin-bottom: 0 !important;
box-shadow: none !important;
}
/* Séparateur entre les panels */
.panel-group .panel + .panel {
border-top: 1px solid var(--kr-border-default) !important;
}
/* Header du groupe - style barre */
.panel-group .panel-heading {
background: var(--kr-bg-surface) !important;
border: none !important;
border-radius: 0 !important;
padding: 12px 0 !important;
margin: 0 !important;
align-items: center !important;
transition: background-color var(--transition-fast) !important;
}
.panel-group .panel-heading:active {
background: var(--kr-bg-hover) !important;
}
/* Contenu du groupe */
.panel-group .panel-collapse {
border: none !important;
}
.panel-group .panel-body {
background: var(--kr-bg-surface) !important;
border: none !important;
padding: 10px 0 !important;
}
/* Titre du groupe */
.panel-group .panel-title {
font-size: 14px !important;
font-weight: 500 !important;
color: var(--kr-text-primary) !important;
}
.panel-group .panel-title a {
display: block !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
}
.panel-group .panel-title a:hover {
color: var(--kr-text-primary) !important;
}
/* ========================================================================
MODAL / DIALOG MOBILE OPTIMIZATIONS
======================================================================== */
/* Modal backdrop - assombrir légèrement */
body.mobile-mode .modal-backdrop {
background-color: rgba(0, 0, 0, 0.6) !important;
}
/* Modal dialog - pleine largeur avec marges minimales */
body.mobile-mode .modal-dialog {
width: 100% !important;
max-width: calc(100vw - 20px) !important;
margin: 10px !important;
max-height: calc(100vh - 20px) !important;
}
/* Modal content - optimiser l'espace */
body.mobile-mode .modal-content {
border-radius: 8px !important;
}
/* Modal header - plus compact */
body.mobile-mode .modal-header {
padding: 15px !important;
}
body.mobile-mode .modal-header h3 {
font-size: 18px !important;
margin: 0 !important;
}
/* Close button - plus grand pour touch */
body.mobile-mode .modal-header .close,
body.mobile-mode .bootbox-close-button {
width: 44px !important;
height: 44px !important;
padding: 10px !important;
font-size: 28px !important;
line-height: 24px !important;
opacity: 0.7 !important;
}
/* Modal body - plus de padding */
body.mobile-mode .modal-body {
padding: 15px !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
}
/* Select dropdown - pleine largeur et plus grand */
body.mobile-mode .modal-body select {
width: 100% !important;
height: 44px !important;
font-size: 16px !important;
padding: 10px !important;
margin-bottom: 15px !important;
border-radius: 6px !important;
}
/* Nav tabs (actions buttons) - layout vertical pour mobile */
body.mobile-mode .modal-body .nav-tabs {
display: flex !important;
flex-wrap: wrap !important;
gap: 10px !important;
border: none !important;
margin-bottom: 20px !important;
}
body.mobile-mode .modal-body .nav-tabs > li {
flex: 1 1 calc(50% - 5px) !important;
min-width: 0 !important;
margin: 0 !important;
border: none !important;
}
body.mobile-mode .modal-body .nav-tabs > li > a {
display: flex !important;
align-items: center !important;
justify-content: center !important;
min-height: 44px !important;
padding: 12px 10px !important;
font-size: 15px !important;
font-weight: 500 !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: 6px !important;
background: var(--kr-bg-secondary) !important;
color: var(--kr-text-primary) !important;
text-align: center !important;
overflow-wrap: break-word !important;
transition: all 0.2s ease !important;
}
body.mobile-mode .modal-body .nav-tabs > li.active > a,
body.mobile-mode .modal-body .nav-tabs > li > a:hover,
body.mobile-mode .modal-body .nav-tabs > li > a:focus {
background: var(--kr-primary) !important;
color: var(--kr-surface) !important;
border-color: var(--kr-primary) !important;
}
/* Form groups - meilleur espacement */
body.mobile-mode .modal-body .form-group {
margin-bottom: 20px !important;
}
body.mobile-mode .modal-body label {
font-size: 15px !important;
font-weight: 500 !important;
margin-bottom: 8px !important;
display: block !important;
}
/* Inputs et textareas - plus grands */
body.mobile-mode .modal-body input[type="text"],
body.mobile-mode .modal-body input[type="number"],
body.mobile-mode .modal-body textarea {
width: 100% !important;
min-height: 44px !important;
font-size: 16px !important;
padding: 10px !important;
border-radius: 6px !important;
}
body.mobile-mode .modal-body textarea {
min-height: 120px !important;
resize: vertical !important;
}
/* Checkboxes et radios - plus grands touch targets */
body.mobile-mode .modal-body input[type="checkbox"],
body.mobile-mode .modal-body input[type="radio"] {
width: 20px !important;
height: 20px !important;
margin-right: 10px !important;
}
body.mobile-mode .modal-body .checkbox label,
body.mobile-mode .modal-body .radio label {
display: flex !important;
align-items: center !important;
min-height: 44px !important;
padding: 8px 0 !important;
cursor: pointer !important;
}
/* Tables - améliorer lisibilité */
body.mobile-mode .modal-body table {
font-size: 14px !important;
}
body.mobile-mode .modal-body table td,
body.mobile-mode .modal-body table th {
padding: 10px 8px !important;
}
body.mobile-mode .modal-body table th {
text-align: center !important;
}
/* Modal footer - sticky en bas avec boutons pleine largeur */
body.mobile-mode .modal-footer {
padding: 15px !important;
display: flex !important;
gap: 10px !important;
border-top: 1px solid var(--kr-border-default) !important;
background: var(--kr-bg-surface) !important;
}
body.mobile-mode .modal-footer .btn {
flex: 1 !important;
min-height: 48px !important;
max-height: 48px !important;
font-size: 16px !important;
font-weight: 600 !important;
border-radius: 6px !important;
padding: 12px 20px !important;
box-sizing: border-box !important;
line-height: 1.2 !important;
}
body.mobile-mode .modal-footer .btn-primary {
order: 2 !important;
}
body.mobile-mode .modal-footer .btn-default {
order: 1 !important;
}
/* Alert dans modal - meilleur affichage */
body.mobile-mode .modal-body .alert {
padding: 12px !important;
font-size: 14px !important;
line-height: 1.5 !important;
margin-bottom: 15px !important;
border-radius: 6px !important;
}
/* Avatar/Image dans header - optimiser */
body.mobile-mode .modal-header img {
max-width: 60px !important;
max-height: 60px !important;
border-radius: 4px !important;
}
}
/* ========================================================================
MODAL PERSONNAGE MOBILE - UX/UI OPTIMISÉE POUR STRUCTURE RÉELLE
======================================================================== */
@media (width <= 767px) {
/* ===== 1. SELECT PERSONNAGE - visible, zone tactile agrandie ===== */
body.mobile-mode .bootbox.modal select[name="top"].form-control {
min-height: 56px !important;
font-size: 16px !important;
padding: 12px 16px !important;
border-radius: 8px !important;
border: 2px solid var(--kr-border-default) !important;
background-color: var(--kr-bg-elevated) !important;
transition: border-color 0.2s ease !important;
}
body.mobile-mode .bootbox.modal select[name="top"].form-control:focus {
border-color: var(--kr-primary) !important;
outline: none !important;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1) !important;
}
/* Conteneur du select avec marges */
body.mobile-mode .bootbox.modal select[name="top"].form-control + br,
body.mobile-mode .bootbox.modal .row:has(> .col-md-6 > select[name="top"]) {
margin-bottom: 16px !important;
}
/* ===== 2. HEADER H3 - avatar + nom compact ===== */
body.mobile-mode .bootbox.modal h3 {
display: flex !important;
align-items: center !important;
gap: 12px !important;
padding: 12px 16px !important;
margin: 0 0 16px !important;
background: var(--kr-bg-surface) !important;
border-radius: 8px !important;
font-size: 18px !important;
line-height: 1.3 !important;
}
/* Avatar 48px */
body.mobile-mode .bootbox.modal h3 img[width="80"] {
width: 48px !important;
height: 48px !important;
max-width: 48px !important;
max-height: 48px !important;
border-radius: 6px !important;
flex-shrink: 0 !important;
order: -1 !important;
}
/* Wrapper pull-right dans h3 */
body.mobile-mode .bootbox.modal h3 .pull-right {
float: none !important;
order: -1 !important;
margin: 0 !important;
}
/* Liens mini (profil + kramail) */
body.mobile-mode .bootbox.modal h3 .mini.center {
display: flex !important;
gap: 8px !important;
margin: 0 !important;
justify-content: flex-start !important;
}
body.mobile-mode .bootbox.modal h3 .mini.center a {
min-width: 44px !important;
min-height: 44px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Bouton groupe (user-friends) */
body.mobile-mode .bootbox.modal h3 .btn.btn-xs {
min-width: 44px !important;
min-height: 44px !important;
padding: 8px !important;
margin-left: auto !important;
}
/* ===== 3. NAVIGATION TABS - fusionner les deux listes en une seule ===== */
/* Conteneur des listes */
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .panel-heading {
display: flex !important;
flex-flow: row nowrap !important;
overflow: hidden auto !important;
-webkit-overflow-scrolling: touch !important;
scrollbar-width: none !important;
-ms-overflow-style: none !important;
border-bottom: 2px solid var(--kr-border-default) !important;
padding: 0 !important;
margin: 0 !important;
background: var(--kr-bg-surface) !important;
}
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .panel-heading::-webkit-scrollbar {
display: none !important;
}
/* Chaque liste .nav-tabs inline */
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs {
display: flex !important;
flex-wrap: nowrap !important;
flex-shrink: 0 !important;
border: none !important;
padding: 0 !important;
gap: 0 !important;
margin: 0 !important;
background: transparent !important;
position: relative !important;
}
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs::-webkit-scrollbar {
display: none !important;
}
/* Tabs individuels - min 88px tactiles */
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li {
flex: 0 0 auto !important;
min-width: 88px !important;
width: auto !important;
height: 44px !important;
margin: 0 !important;
border: none !important;
}
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li > a {
padding: 10px 16px !important;
min-height: 44px !important;
height: 44px !important;
font-size: 14px !important;
font-weight: 500 !important;
line-height: 1.2 !important;
white-space: nowrap !important;
border-radius: 0 !important;
border: none !important;
border-bottom: 3px solid transparent !important;
background: transparent !important;
color: var(--kr-text-secondary) !important;
transition: all 0.2s ease !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
text-align: center !important;
margin: 0 !important;
}
/* Tab active */
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li.active > a,
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li.active > a:hover,
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li.active > a:focus {
background: var(--kr-bg-elevated) !important;
color: var(--kr-primary) !important;
border: none !important;
border-bottom: 3px solid var(--kr-primary) !important;
font-weight: 600 !important;
height: 44px !important;
}
/* Feedback tactile tabs */
body.mobile-mode .bootbox.modal .panel.with-nav-tabs .nav-tabs > li > a:active {
transform: scale(0.96) !important;
background: var(--kr-bg-active) !important;
}
/* ===== 4. PANEL BODY - padding optimisé ===== */
body.mobile-mode .bootbox.modal .panel-body.panel-order {
padding: 16px !important;
}
/* ===== 5. COLONNES FUSIONNÉES EN MODE MOBILE ===== */
/* Les colonnes sont fusionnées en une seule div par JavaScript */
body.mobile-mode .bootbox.modal .merged-columns {
padding: 12px !important;
font-size: 14px !important;
line-height: 1.6 !important;
}
/* Styles pour l'en-tête du tableau */
body.mobile-mode .bootbox.modal .panel-heading {
background: var(--kr-bg-elevated) !important;
border-bottom: 2px solid var(--kr-border-default) !important;
padding: 0 !important;
font-weight: 600 !important;
}
body.mobile-mode .bootbox.modal .panel-heading .merged-columns {
font-size: 13px !important;
color: var(--kr-text-secondary) !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
}
/* Footer (ligne Maladresse/Bonus) et Boutons */
body.mobile-mode .bootbox.modal .panel-footer {
background: var(--kr-bg-elevated) !important;
border-top: 1px solid var(--kr-border-default) !important;
padding: 0 !important;
display: flex !important;
gap: 6px !important;
border: none !important;
}
body.mobile-mode .bootbox.modal .panel-footer label {
margin: 0 !important;
font-size: 14px !important;
}
body.mobile-mode .bootbox.modal .panel-footer select {
padding: 6px 8px !important;
border-radius: 6px !important;
border: 1px solid var(--kr-border-default) !important;
font-size: 14px !important;
}
body.mobile-mode .bootbox.modal .panel-actions .row.form-group {
margin: 0 0 8px !important;
background: var(--kr-bg-elevated) !important;
border: 2px solid var(--kr-border-default) !important;
border-radius: 8px !important;
min-height: 56px !important;
transition: all 0.2s ease !important;
}
/* Row checked */
body.mobile-mode .bootbox.modal .panel-actions .row.form-group:has(input[type="radio"]:checked) {
border-color: var(--kr-primary) !important;
background: var(--kr-alert-info-bg) !important;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1) !important;
}
/* Feedback tactile sur la row */
body.mobile-mode .bootbox.modal .panel-actions .row.form-group:active {
transform: scale(0.98) !important;
background: var(--kr-bg-active) !important;
}
body.mobile-mode .bootbox.modal .panel-actions .row.form-group > div {
padding: 0 8px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Colonne du radio button - aligné à gauche */
body.mobile-mode .bootbox.modal .panel-actions .row.form-group > div.col-sm-1 {
justify-content: flex-start !important;
}
/* Colonne du label - aligné à gauche */
body.mobile-mode .bootbox.modal .panel-actions .row.form-group > div.col-sm-7 {
justify-content: flex-start !important;
}
/* Radio inputs cachés */
body.mobile-mode .bootbox.modal .panel-actions input[type="radio"] {
position: absolute !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
}
/* Labels comme boutons */
body.mobile-mode .bootbox.modal .panel-actions label {
cursor: pointer !important;
font-weight: normal !important;
font-size: 15px !important;
margin: 0 !important;
padding: 14px 0 !important;
display: block !important;
width: 100% !important;
}
/* Label checked */
body.mobile-mode .bootbox.modal .panel-actions input[type="radio"]:checked + label {
font-weight: 600 !important;
}
/* Colonnes diff/jet plus compactes */
body.mobile-mode .bootbox.modal .panel-actions .center {
font-size: 14px !important;
font-weight: 600 !important;
}
/* ===== 6. TOOLBAR - boutons tactiles optimisés ===== */
body.mobile-mode .bootbox.modal .btn-toolbar {
display: grid !important;
grid-template-columns: repeat(6, 1fr) !important;
gap: 4px !important;
margin: 0 0 16px !important;
padding: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group {
display: contents !important;
}
/* Supprimer COMPLÈTEMENT les pseudo-éléments clearfix de Bootstrap 3 */
body.mobile-mode .bootbox.modal .btn-toolbar::before,
body.mobile-mode .bootbox.modal .btn-toolbar::after,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group::before,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group::after,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown::before,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown::after,
body.mobile-mode .bootbox.modal .btn-toolbar span.dropdown::before,
body.mobile-mode .bootbox.modal .btn-toolbar span.dropdown::after {
content: none !important;
display: none !important;
width: 0 !important;
height: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
/* Tous les boutons directs et liens dans la toolbar */
body.mobile-mode .bootbox.modal .btn-toolbar > .btn,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group > a.btn,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group > button.btn,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group > .dropdown {
min-width: 44px !important;
max-width: none !important;
width: 100% !important;
min-height: 44px !important;
max-height: 44px !important;
height: 44px !important;
padding: 8px !important;
margin: 0 !important;
font-size: 18px !important;
line-height: 1 !important;
border-radius: 4px !important;
border-width: 1px !important;
display: grid !important;
place-items: center !important;
transition: all 0.2s ease !important;
box-sizing: border-box !important;
}
/* Dropdown wrapper - ne pas appliquer de style sur le span dropdown */
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown {
display: contents !important;
position: relative !important;
}
/* Le lien dropdown-toggle à l'intérieur du span dropdown */
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown > a.dropdown-toggle {
min-width: 44px !important;
width: 100% !important;
min-height: 44px !important;
height: 44px !important;
padding: 0 !important;
margin: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
border: none !important;
background: transparent !important;
text-decoration: none !important;
}
/* Le bouton à l'intérieur du dropdown-toggle */
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown > a.dropdown-toggle > .btn {
min-width: 44px !important;
width: 100% !important;
min-height: 44px !important;
height: 44px !important;
padding: 8px !important;
margin: 0 !important;
font-size: 18px !important;
line-height: 1 !important;
border-radius: 4px !important;
border-width: 1px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-sizing: border-box !important;
}
/* Feedback tactile sur tous les boutons */
body.mobile-mode .bootbox.modal .btn-toolbar .btn:active,
body.mobile-mode .bootbox.modal .btn-toolbar .btn-group > a.btn:active,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown > a.dropdown-toggle:active,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown > a.dropdown-toggle > .btn:active {
transform: scale(0.95) !important;
background: var(--kr-bg-active) !important;
opacity: 0.8 !important;
}
/* Dropdown smiley/couleurs - doit apparaître PAR-DESSUS la modal */
/* NOTE CRITIQUE: Bootstrap JS force position:fixed dynamiquement */
/* Solution: Sélecteur ultra-spécifique + !important pour override Bootstrap */
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown .dropdown-menu,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown-menu {
max-width: 90vw !important;
max-height: 50vh !important;
overflow-y: auto !important;
padding: 12px !important;
/* CRITIQUE: Garder position: absolute pour le positionnement relatif Bootstrap */
/* Bootstrap JS essaie de forcer position:fixed - on l'override ici */
position: absolute !important;
/* z-index élevé pour passer au-dessus du footer modal */
z-index: 10000 !important;
/* Forcer l'affichage VERS LE HAUT (dropup behavior) */
bottom: 100% !important;
top: auto !important;
/* Petit espace entre le bouton et le dropdown */
margin-bottom: 4px !important;
}
/* CRITIQUE: Forcer display: block quand le dropdown est ouvert */
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown.open .dropdown-menu,
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown:has([aria-expanded="true"]) .dropdown-menu {
display: block !important;
}
/* SOLUTION: Forcer tous les parents à ne PAS créer de contexte d'empilement */
/* Cela permet au dropdown avec z-index: 10000 de passer au-dessus du footer */
body.mobile-mode .bootbox.modal .modal-dialog,
body.mobile-mode .bootbox.modal .modal-content,
body.mobile-mode .bootbox.modal .modal-body,
body.mobile-mode .bootbox.modal .modal-footer,
body.mobile-mode .bootbox.modal .btn-toolbar,
body.mobile-mode .bootbox.modal .dropdown {
/* Enlever TOUTES les propriétés qui créent un nouveau contexte d'empilement */
transform: none !important;
filter: none !important;
perspective: none !important;
will-change: auto !important;
contain: none !important;
/* NE PAS mettre de z-index ici, sinon ça crée un nouveau contexte */
z-index: auto !important;
}
/* CRITIQUE: Forcer overflow: visible sur les parents pour ne PAS couper le dropdown */
body.mobile-mode .bootbox.modal .modal-content,
body.mobile-mode .bootbox.modal .modal-body,
body.mobile-mode .bootbox.modal .panel-body {
overflow: visible !important;
}
/* La modal racine doit garder son z-index pour le backdrop */
body.mobile-mode .bootbox.modal {
z-index: 1050 !important;
}
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown-menu table {
width: 100% !important;
}
body.mobile-mode .bootbox.modal .btn-toolbar .dropdown-menu table td a {
min-width: 44px !important;
min-height: 44px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Backdrop du dropdown sous le dropdown lui-même */
body.mobile-mode .bootbox.modal .dropdown-backdrop {
z-index: 1049 !important;
}
/* ===== 7. TEXTAREA - 120px min + compteur visible ===== */
body.mobile-mode .bootbox.modal textarea#message,
body.mobile-mode .bootbox.modal textarea.form-control {
font-size: 16px !important;
line-height: 1.5 !important;
padding: 12px !important;
border-radius: 8px !important;
min-height: 120px !important;
border: 2px solid var(--kr-border-default) !important;
transition: border-color 0.2s ease !important;
}
body.mobile-mode .bootbox.modal textarea#message:focus,
body.mobile-mode .bootbox.modal textarea.form-control:focus {
border-color: var(--kr-primary) !important;
outline: none !important;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1) !important;
}
/* Compteur */
body.mobile-mode .bootbox.modal .pull-right:has(#car),
body.mobile-mode .bootbox.modal #car {
font-size: 13px !important;
font-weight: 500 !important;
color: var(--kr-text-secondary) !important;
margin-top: 4px !important;
}
/* ===== 8. FOOTER - sticky avec bouton OK 2x ===== */
body.mobile-mode .bootbox.modal .modal-footer {
position: sticky !important;
bottom: 0 !important;
display: flex !important;
gap: 10px !important;
padding: 12px 16px !important;
padding-bottom: max(12px, env(safe-area-inset-bottom)) !important;
background: var(--kr-bg-surface) !important;
border-top: 1px solid var(--kr-border-default) !important;
box-shadow: 0 -2px 8px rgba(0,0,0,0.08) !important;
flex-shrink: 0 !important;
margin: 0 !important;
z-index: 100 !important; /* Sous le dropdown (z-index: 1050) */
}
body.mobile-mode .bootbox.modal .modal-footer .btn {
margin: 0 !important;
min-height: 50px !important;
font-size: 16px !important;
font-weight: 600 !important;
border-radius: 8px !important;
transition: all 0.2s ease !important;
border: none !important;
}
/* Bouton Cancel - 1x */
body.mobile-mode .bootbox.modal .modal-footer .btn-default {
flex: 1 !important;
order: 1 !important;
}
/* Bouton OK - 2x plus large */
body.mobile-mode .bootbox.modal .modal-footer .btn-primary {
flex: 2 !important;
order: 2 !important;
min-height: 52px !important;
}
body.mobile-mode .bootbox.modal .modal-footer .btn:active {
transform: scale(0.97) !important;
}
}
/* ============================================================================
MOBILE - PAGE PROFIL/INTERFACE
============================================================================ */
@media (width <= 767px) {
/* Correction de l'affichage des sélecteurs de thème */
body.mobile-mode #kr-tamper-theme-form .form-group {
margin-bottom: 20px;
}
/* Grid pour les options de thème avec images */
body.mobile-mode #kr-tamper-theme-form .form-group > div[style*="padding-left"] {
display: grid !important;
grid-template-columns: 1fr !important;
gap: 12px !important;
padding: 0 !important;
}
/* Chaque option de thème (conteneur label + image) */
body.mobile-mode #kr-tamper-theme-form .form-group > div > div {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
gap: 8px !important;
padding: 12px !important;
background: rgba(255, 255, 255, 0.05) !important;
border-radius: 8px !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* Cacher l'icône float left qui crée un conflit */
body.mobile-mode #kr-tamper-theme-form .form-group .lefticon {
display: none !important;
}
/* Image de prévisualisation du thème */
body.mobile-mode #kr-tamper-theme-form .form-group img {
width: 100% !important;
height: auto !important;
display: block !important;
border-radius: 4px !important;
margin: 0 !important;
}
/* Label du thème */
body.mobile-mode #kr-tamper-theme-form .form-group label {
display: flex !important;
align-items: center !important;
gap: 8px !important;
width: 100% !important;
margin: 0 !important;
cursor: pointer !important;
font-size: 14px !important;
line-height: 1.4 !important;
}
/* Radio button */
body.mobile-mode #kr-tamper-theme-form .form-group input[type="radio"] {
position: static !important;
flex-shrink: 0 !important;
margin: 0 !important;
width: 20px !important;
height: 20px !important;
}
/* Checkbox */
body.mobile-mode #kr-tamper-theme-form .form-group input[type="checkbox"] {
position: static !important;
flex-shrink: 0 !important;
margin: 0 !important;
width: 20px !important;
height: 20px !important;
}
/* Label principal de la section */
body.mobile-mode #kr-tamper-theme-form .form-group > label.control-label {
font-size: 16px !important;
font-weight: 600 !important;
margin-bottom: 12px !important;
display: block !important;
width: 100% !important;
}
/* ============================================================================
FORUM RP - TRANSFORMATION EN CARDS STYLE REDDIT EXPLORE
Inspiré de la section "Recommandées pour toi" de Reddit
============================================================================ */
/* Conteneur principal du forum */
body.mobile-mode .panel-default {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
/* Header du panel - titre "Jeu (RP)" */
body.mobile-mode .panel-default > .panel-heading {
background: var(--kr-bg-surface) !important;
border: none !important;
border-radius: var(--mobile-radius) !important;
padding: var(--mobile-spacing-lg) !important;
margin-bottom: var(--mobile-spacing-md) !important;
align-items: center !important;
}
body.mobile-mode .panel-default > .panel-heading h3 {
font-size: 20px !important;
font-weight: 700 !important;
margin: 0 !important;
color: var(--kr-text-primary) !important;
}
/* Cacher le header du tableau (colonnes) */
body.mobile-mode .panel-default .table > thead {
display: none !important;
}
/* Transformer le tbody en container flex vertical */
body.mobile-mode .panel-default .table > tbody {
display: flex !important;
flex-direction: column !important;
gap: 4px !important;
}
/* Chaque ligne devient une card avec CSS Grid pour contrôler l'ordre d'affichage */
body.mobile-mode .panel-default .table > tbody > tr {
display: grid !important;
grid-template-columns: 1fr !important;
grid-template-rows: auto !important;
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: 8px !important;
padding: 6px !important;
box-shadow: var(--kr-shadow-sm) !important;
transition: all var(--transition-fast) !important;
position: relative !important;
min-height: auto !important;
}
/* Effet au tap sur la card */
body.mobile-mode .panel-default .table > tbody > tr:active {
background: var(--kr-bg-hover) !important;
transform: scale(0.98) !important;
}
/* === RÉORGANISATION DES CELLULES AVEC GRID === */
/* Utiliser grid-row pour contrôler l'ordre d'affichage */
body.mobile-mode .panel-default .table > tbody > tr > td {
display: block !important;
border: none !important;
padding: 0 !important;
width: 100% !important;
}
/* td:first-child à la ligne 1 (contient titre, mods, description) */
/* === CELLULE 1 : Titre, Modérateurs, (Description cachée ici) === */
/* Cette cellule contient 3 éléments dont on doit cacher la description pour la réafficher plus tard */
body.mobile-mode .panel-default .table > tbody > tr > td:first-child {
display: flex !important;
flex-direction: column !important;
grid-row: 1 !important;
}
/* td:nth-child(2) et td:nth-child(3) (stats) à la ligne 2 */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(2),
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(3) {
grid-row: 2 !important;
}
/* td:nth-child(4) (dernier message) à la ligne 4 */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) {
grid-row: 4 !important;
}
/* === TITRE DU FORUM === */
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > p:first-child {
order: 1 !important;
margin: 0 !important;
font-size: 0 !important; /* Masquer le texte brut "→" */
}
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > p:first-child > a {
font-size: 15px !important;
font-weight: 700 !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
line-height: 1.1 !important;
display: block !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > p:first-child > a:active {
color: var(--kr-primary) !important;
}
/* === MODÉRATEURS === */
/* Afficher les modérateurs juste après le titre */
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > span.tagforum {
order: 2 !important;
margin: 0 !important;
font-size: 11px !important;
color: var(--kr-text-secondary) !important;
line-height: 1.2 !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > span.tagforum > .lefticon {
display: none !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > span.tagforum a {
color: var(--kr-text-secondary) !important;
text-decoration: none !important;
font-weight: 500 !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > span.tagforum a:active {
color: var(--kr-primary) !important;
text-decoration: underline !important;
}
/* === DESCRIPTION === */
/* La description est maintenant dans un wrapper créé par JavaScript */
body.mobile-mode .panel-default .table > tbody > tr > td:first-child > p:nth-child(2),
body.mobile-mode .panel-default .table > tbody > tr > .forum-description-wrapper > p {
order: 999 !important;
font-size: 12px !important;
color: var(--kr-text-secondary) !important;
line-height: 1.2 !important;
margin: 0 !important;
}
/* Description wrapper */
body.mobile-mode .panel-default .table > tbody > tr > .forum-description-wrapper {
margin: 0 0 6px !important;
}
/* === CELLULE 2 & 3 : Stats (Sujets et Messages) === */
/* Les stats sont maintenant dans un wrapper créé par JavaScript */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(2),
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(3),
body.mobile-mode .panel-default .table > tbody > tr > .forum-stats-wrapper > td {
display: inline !important;
font-size: 11px !important;
color: var(--kr-text-secondary) !important;
order: 2 !important;
margin: 0 !important;
padding: 0 !important;
}
/* Stats wrapper */
body.mobile-mode .panel-default .table > tbody > tr > .forum-stats-wrapper {
margin: 0 0 2px !important;
}
/* Ajouter "sujets · " après le premier nombre */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(2)::after,
body.mobile-mode .panel-default .table > tbody > tr > .forum-stats-wrapper > td:first-child::after {
content: " sujets · " !important;
}
/* Ajouter "messages" après le second nombre */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(3)::after,
body.mobile-mode .panel-default .table > tbody > tr > .forum-stats-wrapper > td:nth-child(2)::after {
content: " messages" !important;
}
/* === CELLULE 4 : Dernier message === */
/* Maintenant que le DOM est réorganisé, la cellule du dernier message est à la fin */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4),
body.mobile-mode .panel-default .table > tbody > tr > td:last-child {
order: 5 !important;
padding-top: 3px !important;
border-top: 1px solid var(--kr-border-default) !important;
margin-top: 0 !important;
font-size: 11px !important;
color: var(--kr-text-secondary) !important;
display: block !important;
}
/* Préfixe "→" avant l'auteur */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4)::before,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child::before {
content: "→ " !important;
color: var(--kr-text-secondary) !important;
font-weight: 400 !important;
margin-right: 4px !important;
}
/* Auteur du dernier message - inline */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > a,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > a {
font-size: 12px !important;
font-weight: 500 !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
display: inline !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > a:active,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > a:active {
color: var(--kr-primary) !important;
text-decoration: underline !important;
}
/* Séparateur avant le timestamp */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > a::after,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > a::after {
content: " · " !important;
color: var(--kr-text-muted) !important;
font-weight: 400 !important;
}
/* Timestamp et lien vers le message - inline, pas de bouton */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > p,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > p {
display: inline !important;
margin: 0 !important;
font-size: 13px !important;
color: var(--kr-text-secondary) !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > p > a,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > p > a {
color: var(--kr-text-secondary) !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
background: none !important;
border: none !important;
padding: 0 !important;
box-shadow: none !important;
min-width: 20px !important;
min-height: 20px !important;
}
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > p > a:active,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > p > a:active {
color: var(--kr-primary) !important;
}
/* Icône flèche dans le lien vers le message - 20x20px */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) > p > a .glyphicon,
body.mobile-mode .panel-default .table > tbody > tr > td:last-child > p > a .glyphicon {
display: inline-block !important;
width: 20px !important;
height: 20px !important;
font-size: 14px !important;
line-height: 20px !important;
vertical-align: middle !important;
}
/* Badges/images dans les noms d'utilisateurs */
body.mobile-mode .panel-default .table > tbody > tr > td:nth-child(4) img {
vertical-align: middle !important;
margin: 0 2px !important;
max-height: 16px !important;
}
}
/* ============================================================================
MOBILE MENU DROPDOWNS - Bootstrap 3 native handling
============================================================================ */
@media (width <= 767px) {
/* EN MOBILE : Afficher les dropdown-menu avec la structure Bootstrap 3 */
/* Les dropdown-menu doivent être visibles quand le parent a la classe 'open' */
.navbar-collapse .dropdown.open > .dropdown-menu {
display: block !important;
position: static !important;
float: none !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
box-shadow: none !important;
background-color: transparent !important;
}
/* Styling des items dans le dropdown */
.navbar-collapse .dropdown.open > .dropdown-menu > li > a {
display: block !important;
padding: 10px 15px 10px 25px !important;
clear: both !important;
font-weight: 400 !important;
line-height: 1.4286 !important;
color: var(--kr-text-primary) !important;
white-space: nowrap !important;
background-color: transparent !important;
border-left: 4px solid transparent !important;
}
.navbar-collapse .dropdown.open > .dropdown-menu > li > a:hover,
.navbar-collapse .dropdown.open > .dropdown-menu > li > a:focus {
color: var(--kr-text-primary) !important;
background-color: rgba(0, 0, 0, 0.05) !important;
border-left-color: var(--kr-primary) !important;
}
.navbar-collapse .dropdown.open > .dropdown-menu > li > a:active {
color: var(--kr-primary) !important;
background-color: rgba(0, 0, 0, 0.08) !important;
border-left-color: var(--kr-primary) !important;
}
/* Dividers dans le dropdown */
.navbar-collapse .dropdown.open > .dropdown-menu > .divider {
height: 1px !important;
margin: 9px 0 !important;
background-color: var(--kr-border-default) !important;
}
/* Dropdown headers */
.navbar-collapse .dropdown.open > .dropdown-menu > .dropdown-header {
display: block !important;
padding: 3px 15px 3px 25px !important;
font-size: 12px !important;
line-height: 1.4286 !important;
color: var(--kr-text-muted) !important;
white-space: nowrap !important;
text-transform: uppercase !important;
font-weight: 600 !important;
}
/* Active items in dropdown */
.navbar-collapse .dropdown.open > .dropdown-menu > .active > a,
.navbar-collapse .dropdown.open > .dropdown-menu > .active > a:hover,
.navbar-collapse .dropdown.open > .dropdown-menu > .active > a:focus {
color: var(--kr-primary) !important;
background-color: rgba(0, 0, 0, 0.1) !important;
border-left-color: var(--kr-primary) !important;
}
/* Bootstrap 3 collapse pour les items principaux du menu */
.navbar-collapse .navbar-nav > li > a {
padding: 15px !important;
}
/* Assurer que le caret du dropdown-toggle est visible */
.navbar-collapse .dropdown > a.dropdown-toggle .caret {
border-top-color: currentcolor !important;
border-bottom-color: transparent !important;
}
/* Quand ouvert, inverser le caret */
.navbar-collapse .dropdown.open > a.dropdown-toggle .caret {
border-top-color: transparent !important;
border-bottom-color: currentcolor !important;
transform: rotate(180deg) !important;
}
}
/* ========================================
FORUM CARDS MOBILE
Design cards-based pour liste des forums
======================================== */
/* Container des cards */
.forums-cards-container {
display: none; /* Caché par défaut (desktop) */
padding: 12px 8px;
background: var(--kr-bg-page);
}
/* Affichage mobile uniquement */
.mobile-mode .forums-cards-container {
display: block;
}
/* Masquer le tableau sur mobile */
.mobile-mode table[data-mobile-hidden="true"] {
display: none !important;
}
/* === CARD COMPONENT === */
.forum-card {
background: var(--kr-bg-surface);
border-radius: 12px;
margin: 0 12px 12px;
box-shadow: var(--kr-shadow-sm);
transition: box-shadow 0.2s ease, transform 0.1s ease;
overflow: hidden;
border: 1px solid var(--kr-border-default);
}
/* Forcer l'alignement à gauche pour TOUS les descendants */
.forum-card *,
.forum-card-link *,
.forum-card-header *,
.forum-footer * {
text-align: left !important;
}
.forum-card:active {
transform: scale(0.98);
box-shadow: var(--kr-shadow-md);
}
.forum-card-link {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
justify-content: flex-start !important;
padding: 16px;
text-decoration: none;
color: inherit;
/* Touch target minimum 44px */
min-height: 100px;
/* Active state feedback */
-webkit-tap-highlight-color: rgba(139, 15, 14, 0.05);
}
.forum-card-link:hover,
.forum-card-link:focus {
text-decoration: none;
color: inherit;
}
/* === HEADER === */
.forum-card-header {
margin-bottom: 8px;
text-align: left;
}
.forum-title {
font-size: 18px;
font-weight: 700;
color: var(--kr-text-primary);
line-height: 1.3;
margin: 0;
padding: 0;
text-align: left !important;
}
/* === DESCRIPTION === */
.forum-description {
font-size: 14px;
color: var(--kr-text-secondary);
line-height: 1.5;
margin: 0 0 8px;
text-align: left !important;
/* Limitation à 2 lignes */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* === MODERATEURS === */
.forum-moderators {
font-size: 12px;
color: var(--kr-text-muted);
margin: 0 0 10px;
font-style: normal;
font-weight: 400;
text-align: left !important;
}
/* === FOOTER (Stats + Dernière activité) === */
.forum-footer {
border-top: 1px solid var(--kr-border-default);
padding-top: 10px;
margin-top: auto;
text-align: left !important;
}
.forum-stats {
font-size: 13px;
color: var(--kr-text-muted);
margin-bottom: 6px;
white-space: nowrap;
text-align: left !important;
}
.forum-topics,
.forum-messages {
font-weight: 600;
color: var(--kr-text-secondary);
}
.forum-separator {
margin: 0 4px;
color: var(--kr-text-muted);
}
.forum-last-activity {
font-size: 13px;
color: var(--kr-text-secondary);
line-height: 1.5;
text-align: left !important;
}
.last-user {
font-weight: 600;
color: var(--kr-text-primary);
}
.last-time {
color: var(--kr-text-muted);
font-size: 12px;
}
/* === ÉTATS SPÉCIAUX === */
/* Forum sans activité */
.forum-card:has(.forum-topics:empty) {
opacity: 0.6;
}
/* Accessibilité: focus visible */
.forum-card-link:focus {
outline: 2px solid #2196F3;
outline-offset: 2px;
}
/* === MINI-CHAT FAB === */
.mini-chat-fab {
position: fixed;
bottom: 80px;
right: 16px;
width: 56px;
height: 56px;
background: #2196F3;
color: white;
border: none;
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
cursor: pointer;
font-size: 14px;
font-weight: 700;
transition: all 0.3s ease;
}
.mini-chat-fab:active {
transform: scale(0.95);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.mini-chat-fab.active {
background: #f44336;
}
.mini-chat-fab .fab-text {
display: block;
line-height: 1;
}
/* Mini-chat en overlay fullscreen */
.mobile-mode .mini-chat-overlay {
position: fixed !important;
inset: 56px 0 0 !important; /* Sous le header */
width: 100% !important;
height: auto !important;
z-index: 999 !important;
background: white;
}
/* === RESPONSIVE === */
/* Tablettes en mode portrait */
@media (width >= 768px) and (width <= 1024px) and (orientation: portrait) {
.forum-card {
margin: 12px 24px;
}
.forum-card-link {
padding: 20px;
}
}
/* Desktop: désactiver les cards */
@media (width >= 768px) {
.forums-cards-container {
display: none !important;
}
table[data-mobile-hidden="true"] {
display: table !important;
}
.mini-chat-fab {
display: none;
}
}
/* ============================================================================
MOBILE OPTIMISATION - FORUM SUJETS (DataTables)
============================================================================ */
@media (max-width: 767px) {
/* === CONTRÔLES DATATABLE (Recherche, Affichage, Pagination) === */
/* Wrapper général */
.dataTables_wrapper {
padding: 0 !important;
}
/* Section contrôles haut (Afficher X lignes + Recherche) */
.dataTables_length,
.dataTables_filter {
display: block !important;
width: 100% !important;
margin: 0 0 var(--mobile-spacing-md) !important;
text-align: left !important;
float: none !important;
}
/* Label "Afficher lignes" */
.dataTables_length label {
display: flex !important;
align-items: center !important;
gap: 8px !important;
font-size: 14px !important;
margin: 0 !important;
}
/* Dropdown "Afficher X lignes" - WCAG 2.5.5 (44px min) */
.dataTables_length select {
min-height: var(--mobile-touch-target) !important;
padding: 10px 32px 10px 12px !important;
font-size: 16px !important; /* Évite zoom iOS */
border: 1px solid var(--kr-border-default) !important;
border-radius: var(--mobile-radius) !important;
background-color: var(--kr-bg-surface) !important;
flex: 0 0 auto !important;
min-width: 80px !important;
}
/* Label recherche */
.dataTables_filter label {
display: flex !important;
align-items: center !important;
gap: 8px !important;
width: 100% !important;
margin: 0 !important;
font-size: 14px !important;
}
/* Input recherche - WCAG 2.5.5 (44px min) */
.dataTables_filter input {
flex: 1 !important;
min-height: var(--mobile-touch-target) !important;
padding: 10px 16px !important;
font-size: 16px !important; /* Évite zoom iOS */
border: 1px solid var(--kr-border-default) !important;
border-radius: var(--mobile-radius) !important;
background-color: var(--kr-bg-surface) !important;
width: 100% !important;
}
/* Focus sur inputs */
.dataTables_filter input:focus,
.dataTables_length select:focus {
outline: 2px solid var(--kr-primary) !important;
outline-offset: 2px !important;
border-color: var(--kr-primary) !important;
}
/* === TABLEAU → CARDS === */
/* Masquer le header du tableau */
table.dataTable thead {
display: none !important;
}
/* Transformer tbody en flex vertical */
table.dataTable tbody {
display: flex !important;
flex-direction: column !important;
gap: var(--mobile-spacing-md) !important;
}
/* Chaque devient une card */
table.dataTable tbody tr {
display: flex !important;
flex-direction: column !important;
background: var(--kr-bg-surface) !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: var(--mobile-radius) !important;
padding: 0 !important;
margin: 0 !important;
box-shadow: var(--kr-shadow-sm) !important;
overflow: hidden !important;
transition: box-shadow var(--transition-fast) !important;
}
/* Hover effect sur card (optionnel, pas prioritaire sur tactile) */
table.dataTable tbody tr:hover {
box-shadow: var(--kr-shadow-md) !important;
}
/* === COLONNES VISIBLES === */
/* Masquer colonnes Msg, Vus, Auteur, Dernier Message (affichées autrement) */
table.dataTable tbody td:nth-child(2), /* Msg */
table.dataTable tbody td:nth-child(3), /* Vus */
table.dataTable tbody td:nth-child(4), /* Auteur */
table.dataTable tbody td:nth-child(5) /* Dernier Message */ {
display: none !important;
}
/* Colonne Sujet (zone unique, comme Sujets permanents) */
table.dataTable tbody td:nth-child(1) {
display: flex !important;
flex-direction: column !important;
padding: var(--mobile-spacing-md) !important;
min-height: var(--mobile-touch-target) !important;
gap: var(--mobile-spacing-sm) !important;
}
/* === ZONE 1 : TITRE DU SUJET === */
/* Conteneur titre + icône non-lu */
table.dataTable tbody td:nth-child(1) p {
display: flex !important;
align-items: flex-start !important;
gap: 8px !important;
margin: 0 !important;
}
/* Titre du sujet (lien principal vers page 1) */
table.dataTable tbody td:nth-child(1) p a {
flex: 1 !important;
font-size: 16px !important;
font-weight: 600 !important;
line-height: 1.4 !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
display: block !important;
padding: 4px 0 !important;
}
/* Feedback tactile sur titre */
table.dataTable tbody td:nth-child(1) p a:active {
color: var(--kr-primary) !important;
background-color: rgba(139, 15, 14, 0.05) !important;
}
/* === ICÔNES ET PAGINATION : MASQUÉES === */
/* Masquer l'icône folder-open (1ère UL) */
table.dataTable tbody td:nth-child(1) > ul:first-of-type {
display: none !important;
}
/* Masquer la pagination (2ème UL) */
table.dataTable tbody td:nth-child(1) > ul:nth-of-type(2) {
display: none !important;
}
/* === ZONE 1 : TITRE DU SUJET === */
/* Titre du sujet */
table.dataTable tbody td:nth-child(1) p {
margin: 0 0 8px 0 !important;
padding: 0 !important;
}
/* Lien titre */
table.dataTable tbody td:nth-child(1) p a {
font-size: 16px !important;
font-weight: 600 !important;
line-height: 1.4 !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
display: block !important;
}
/* Feedback tactile sur titre */
table.dataTable tbody td:nth-child(1) p a:active {
color: var(--kr-primary) !important;
opacity: 0.7 !important;
}
/* Tags */
table.dataTable tbody td:nth-child(1) .forum-tags {
display: flex !important;
flex-wrap: wrap !important;
gap: 3px !important;
margin-top: 6px !important;
}
table.dataTable tbody td:nth-child(1) .forum-tags .glyphicon {
color: var(--kr-text-muted) !important;
font-size: 8px !important;
}
table.dataTable tbody td:nth-child(1) .forum-tags a {
display: inline-flex !important;
align-items: center !important;
padding: 1px 4px !important;
font-size: 9px !important;
color: var(--kr-text-secondary) !important;
background: var(--kr-bg-hover) !important;
border-radius: 2px !important;
text-decoration: none !important;
transition: all var(--transition-fast) !important;
}
table.dataTable tbody td:nth-child(1) .forum-tags a:active {
background: var(--kr-bg-active) !important;
}
/* === BADGES STATS (Msg + Vus) === */
.forum-topic-stats-mobile {
display: flex !important;
gap: 8px !important;
margin-top: 8px !important;
flex-wrap: wrap !important;
}
.badge-stat {
display: inline-flex !important;
align-items: center !important;
gap: 5px !important;
padding: 5px 10px !important;
font-size: 13px !important;
font-weight: 500 !important;
color: var(--kr-text-secondary) !important;
border-radius: 6px !important;
transition: all var(--transition-fast) !important;
}
.badge-stat i {
font-size: 12px !important;
}
.badge-messages {
background: rgba(33, 150, 243, 0.1) !important;
border: 1px solid rgba(33, 150, 243, 0.2) !important;
color: #1976d2 !important;
}
.badge-views {
background: rgba(76, 175, 80, 0.1) !important;
border: 1px solid rgba(76, 175, 80, 0.2) !important;
color: #388e3c !important;
}
/* === ZONE 2 : DERNIER MESSAGE === */
/* Lien auteur */
table.dataTable tbody td:nth-child(5) a:first-child {
font-size: 14px !important;
font-weight: 600 !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
gap: 4px !important;
}
/* Image nation dans auteur */
table.dataTable tbody td:nth-child(5) a:first-child img {
width: 16px !important;
height: 16px !important;
vertical-align: middle !important;
}
/* Paragraphe date/heure */
table.dataTable tbody td:nth-child(5) p {
font-size: 13px !important;
color: var(--kr-text-muted) !important;
margin: 0 !important;
display: flex !important;
align-items: center !important;
gap: 6px !important;
}
/* Icône lien vers message */
table.dataTable tbody td:nth-child(5) p a .glyphicon {
font-size: 12px !important;
color: var(--kr-primary) !important;
}
/* Feedback tactile zone dernier message */
table.dataTable tbody td:nth-child(5):active {
background: var(--kr-bg-active) !important;
}
/* === AFFICHER STATS (Msg + Vus) EN BADGES === */
/* Créer un conteneur pour stats après le titre */
table.dataTable tbody td:nth-child(1)::after {
content: '';
display: none; /* On va gérer ça avec JS si besoin */
}
/* === PAGINATION === */
/* Wrapper pagination */
.dataTables_paginate {
display: flex !important;
justify-content: center !important;
gap: var(--mobile-spacing-sm) !important;
margin-top: var(--mobile-spacing-lg) !important;
padding: 0 !important;
}
/* Boutons Précédent/Suivant - WCAG 2.5.5 (44px min) */
.dataTables_paginate .paginate_button {
min-height: var(--mobile-touch-target) !important;
min-width: var(--mobile-touch-target) !important;
padding: 10px 16px !important;
font-size: 16px !important;
border: 1px solid var(--kr-border-default) !important;
border-radius: var(--mobile-radius) !important;
background: var(--kr-bg-surface) !important;
color: var(--kr-text-primary) !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
transition: all var(--transition-fast) !important;
}
/* État actif */
.dataTables_paginate .paginate_button.current {
background: var(--kr-primary) !important;
color: white !important;
border-color: var(--kr-primary) !important;
font-weight: 600 !important;
}
/* État désactivé */
.dataTables_paginate .paginate_button.disabled {
opacity: 0.4 !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
/* Feedback tactile */
.dataTables_paginate .paginate_button:not(.disabled):not(.current):active {
background: var(--kr-bg-active) !important;
transform: scale(0.95) !important;
}
/* === INFO PAGINATION (Affiche X à Y de Z lignes) === */
.dataTables_info {
text-align: center !important;
font-size: 14px !important;
color: var(--kr-text-secondary) !important;
margin-top: var(--mobile-spacing-md) !important;
padding: 0 var(--mobile-spacing-md) !important;
}
/* === DARK MODE === */
html[class*="-dark"] table.dataTable tbody tr {
background: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
}
html[class*="-dark"] table.dataTable tbody td:nth-child(1) {
border-bottom-color: var(--kr-border-default) !important;
}
html[class*="-dark"] table.dataTable tbody td:nth-child(5) {
background: rgba(255, 255, 255, 0.03) !important;
}
html[class*="-dark"] .dataTables_filter input,
html[class*="-dark"] .dataTables_length select {
background: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
html[class*="-dark"] .dataTables_paginate .paginate_button {
background: var(--kr-bg-surface) !important;
border-color: var(--kr-border-default) !important;
color: var(--kr-text-primary) !important;
}
/* ============================================================================
MOBILE OPTIMISATION - MESSAGES DE FORUM (THREAD)
Structure restructurée par JS:
- Row 1: col-xs-4 (user) + col-xs-8 (boutons)
- Row 2: col-xs-12 (contenu message)
============================================================================ */
/* === STRUCTURE PRINCIPALE === */
/* Container du post restructuré */
ul.media-list.forum > li.media.forum-post-restructured {
display: block !important;
padding: 12px 8px !important;
margin-bottom: 24px !important;
list-style: none !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}
/* Row 1: Header (user + boutons) */
ul.media-list.forum > li.media .forum-header {
margin-bottom: 12px !important;
margin-left: 0 !important;
margin-right: 0 !important;
display: flex !important;
flex-wrap: nowrap !important;
}
/* Row 2: Contenu */
ul.media-list.forum > li.media .forum-content-row {
margin-left: 0 !important;
margin-right: 0 !important;
}
/* === COLONNE 1: USER INFO === */
.forum-user-section {
padding: 0 8px !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
text-align: center !important;
}
/* Avatar */
.forum-user-section img.avatar,
.forum-user-section .user-info img:first-child {
max-width: 80px !important;
height: auto !important;
border-radius: 4px !important;
margin-bottom: 8px !important;
}
/* Nom d'utilisateur - sans ellipsis, wrap autorisé */
.forum-user-section .cartouche strong,
.forum-user-section strong a {
font-size: 13px !important;
line-height: 1.2 !important;
display: block !important;
white-space: normal !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
hyphens: auto !important;
overflow: visible !important;
text-overflow: clip !important;
max-width: 100% !important;
min-height: 0 !important;
}
/* Conteneur icône de rang + titre (inline) */
.forum-user-section [data-kr-rank-title] {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
gap: 4px !important;
flex-wrap: nowrap !important;
margin-top: 4px !important;
max-width: 100% !important;
}
.forum-user-section [data-kr-rank-title] img {
flex-shrink: 0 !important;
margin: 0 !important;
max-width: 24px !important;
max-height: 24px !important;
}
/* Titre de rang : inline sans ellipsis */
.forum-user-section [data-kr-rank-title] strong {
font-size: 10px !important;
color: #bbb !important;
font-weight: normal !important;
white-space: normal !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
hyphens: auto !important;
overflow: visible !important;
text-overflow: clip !important;
max-width: none !important;
line-height: 1.3 !important;
text-align: center !important;
flex: 1 1 auto !important;
min-width: 0 !important;
}
/* Date mobile ajoutée par JS */
.forum-user-section .post-date-mobile {
font-size: 11px !important;
color: #999 !important;
margin-top: 4px !important;
text-align: center !important;
}
/* Titre/fonction - ellipsis */
.forum-user-section strong:not(.cartouche strong):not([data-kr-rank-title] strong) {
font-size: 10px !important;
color: #bbb !important;
font-weight: normal !important;
margin-top: 4px !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: 100% !important;
}
/* === COLONNE 2: BOUTONS D'ACTION === */
.forum-actions-section {
padding: 0 8px !important;
display: flex !important;
flex-direction: column !important;
align-items: flex-end !important;
gap: 8px !important;
}
/* Container des boutons */
.forum-actions-section .pull-right {
float: none !important;
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: center !important;
gap: 8px !important;
width: 100% !important;
}
/* Groupes de boutons inline */
.forum-actions-section .btn-group {
display: inline-flex !important;
flex-direction: row !important;
gap: 0 !important;
flex-wrap: nowrap !important;
}
/* Boutons carrés 44x44px (WCAG touch target) */
.forum-actions-section .btn {
width: 44px !important;
height: 44px !important;
min-width: 44px !important;
max-width: 44px !important;
padding: 0 !important;
margin: 0 !important;
font-size: 14px !important;
line-height: 44px !important;
border: none !important;
border-radius: 0 !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
/* Override Bootstrap min-width */
.forum-actions-section .btn-group-xs > .btn,
.forum-actions-section .btn-group-xs .btn {
min-width: 44px !important;
}
/* Icônes des boutons */
.forum-actions-section .btn i {
font-size: 11px !important;
line-height: 24px !important;
}
/* === ROW 2: CONTENU DU MESSAGE === */
.forum-content-row {
margin-top: 0 !important;
}
.forum-content-section {
padding: 12px 8px !important;
border-top: 1px dashed rgba(255, 255, 255, 0.15) !important;
font-size: 15px !important;
line-height: 1.6 !important;
width: 100% !important;
}
/* Contenu du message */
.forum-content-section .forum-msg,
.forum-content-section > div {
width: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
/* Citations et blockquotes */
.forum-content-section blockquote {
margin: 8px 0 !important;
padding: 8px 12px !important;
border-left: 3px solid rgba(255, 255, 255, 0.2) !important;
font-size: 14px !important;
}
/* Images dans le contenu */
.forum-content-section img {
max-width: 100% !important;
height: auto !important;
}
/* Spoilers */
.forum-content-section .spoiler {
margin: 8px 0 !important;
}
/* Signature */
.forum-content-section hr,
.forum-content-section .signature {
margin-top: 16px !important;
padding-top: 16px !important;
border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
font-size: 12px !important;
color: #999 !important;
}
/* ============================================================================
11. NOUVEAU SUJET - MOBILE OPTIMIZATION
Optimise la page "Poster un nouveau sujet" pour mobile
- Toolbar BBCode horizontale scrollable
- Inputs/buttons 44px minimum (touch target)
- Font 16px pour éviter zoom iOS
- Feedback visuel clair
============================================================================ */
@media (width <= 768px) {
/* 0. LAYOUT - Fix Bootstrap grid overflow */
/* Ajouter padding latéral pour ne pas coller au bord */
.container,
.container-fluid {
padding-left: 12px !important;
padding-right: 12px !important;
}
/* Réinitialiser les colonnes Bootstrap pour mobile */
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6,
.col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
float: none !important;
width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
/* Réinitialiser les form-group qui contiennent les colonnes */
.form-group {
margin-left: 0 !important;
margin-right: 0 !important;
padding-left: 12px !important;
padding-right: 12px !important;
margin-bottom: var(--mobile-spacing-lg);
}
/* Réinitialiser input-group */
.input-group {
display: block !important;
width: 100% !important;
}
/* CRÍTICA: Contenir les débordements de la toolbar dans le form-group */
.form-group {
overflow: hidden;
width: 100%;
}
.form-group .col-md-10,
.form-group .col-sm-10 {
overflow: hidden;
}
/* 1. TOOLBAR BBCODE - Horizontal scrollable */
.btn-toolbar {
display: flex !important; /* Override Bootstrap */
flex-wrap: nowrap; /* Pas de wrapping */
overflow-x: auto; /* Scrollbar horizontale */
overflow-y: hidden; /* Pas de scrollbar verticale */
gap: 4px;
padding: 8px;
margin-bottom: var(--mobile-spacing-md);
background: rgba(0,0,0,0.02);
border: 1px solid var(--kr-border-default);
border-radius: var(--mobile-radius);
-webkit-overflow-scrolling: touch; /* iOS smooth scrolling */
white-space: nowrap; /* Empêche wrap des groupes */
}
/* Scrollbar styling (webkit browsers) */
.btn-toolbar::-webkit-scrollbar {
height: 4px;
}
.btn-toolbar::-webkit-scrollbar-track {
background: rgba(0,0,0,0.05);
border-radius: 2px;
}
.btn-toolbar::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.15);
border-radius: 2px;
}
/* Groupes de boutons - Layout horizontal */
.btn-toolbar .btn-group {
display: flex !important; /* Override Bootstrap flex-column */
flex-direction: row !important;
flex-shrink: 0; /* Empêche compression */
gap: 0;
margin: 0 !important;
margin-right: 8px;
padding-right: 8px;
border-right: 1px solid var(--kr-border-default);
}
.btn-toolbar .btn-group:last-child {
margin-right: 0;
padding-right: 0;
border-right: none;
}
/* Boutons BBCode - Touch targets 44px */
.btn-toolbar .btn {
min-width: var(--mobile-touch-target) !important;
width: var(--mobile-touch-target);
height: var(--mobile-touch-target);
padding: 0 !important;
margin: 0 !important;
display: flex !important;
align-items: center;
justify-content: center;
font-size: 16px; /* Visible sur petit écran */
border-radius: 6px;
transition: all var(--transition-fast);
flex-shrink: 0;
}
.btn-toolbar .btn:hover {
background: rgba(139, 15, 14, 0.1);
border-color: var(--kr-primary);
color: var(--kr-primary);
}
.btn-toolbar .btn:active {
background: rgba(139, 15, 14, 0.2);
transform: scale(0.95);
}
.btn-toolbar .btn:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(139, 15, 14, 0.1);
border-color: var(--kr-primary);
}
/* 2. FORM CONTROLS - Inputs, textareas, selects */
.form-control {
font-size: 16px !important; /* iOS zoom prevention - CRITIQUE */
min-height: var(--mobile-touch-target) !important;
padding: 12px var(--mobile-spacing-md) !important;
border-radius: var(--mobile-radius) !important;
line-height: 1.5;
}
.form-control:focus {
outline: none;
border-color: var(--kr-primary);
box-shadow: 0 0 0 3px rgba(139, 15, 14, 0.1);
}
/* Textarea - Priorité message */
textarea.form-control {
min-height: 150px !important;
font-family: monospace;
resize: vertical;
line-height: 1.6;
}
/* Select - Styled au même niveau */
select.form-control {
height: auto;
min-height: var(--mobile-touch-target);
}
/* 3. FORM GROUPS - Stack vertical */
.form-group {
margin-bottom: var(--mobile-spacing-lg);
}
.form-group label {
display: block;
margin-bottom: var(--mobile-spacing-sm);
font-weight: 600;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--kr-text);
}
.form-group label .required {
color: var(--kr-primary);
}
/* 4. CHECKBOXES - Inline avec spacing tactile */
.form-group .checkbox {
display: inline-flex;
align-items: center;
gap: var(--mobile-spacing-md);
min-height: var(--mobile-touch-target);
margin-right: var(--mobile-spacing-lg);
margin-bottom: 0;
padding: 8px 0;
}
.form-group .checkbox input[type="checkbox"] {
width: 20px !important;
height: 20px !important;
min-width: 20px;
min-height: 20px;
margin: 0 !important;
cursor: pointer;
accent-color: var(--kr-primary);
flex-shrink: 0;
}
.form-group .checkbox label {
display: inline;
margin: 0;
font-weight: 500;
cursor: pointer;
font-size: 16px;
text-transform: none;
letter-spacing: normal;
padding: 8px 0;
}
/* 5. BOUTON ENVOYER - Sticky footer */
button.btn-primary.pull-right {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 90;
width: 100% !important;
height: 56px !important;
margin: 0 !important;
padding: 12px 16px !important;
border-radius: 0 !important;
font-size: 16px;
font-weight: 600;
border: none !important;
}
button.btn-primary.pull-right:active {
background: var(--kr-primary-dark);
transform: scale(0.98);
}
/* Padding au dernier form-group pour éviter overlap avec footer */
.form-group:last-of-type {
padding-bottom: 60px;
}
/* 6. ACCORDION/COLLAPSE - Options & Sondage */
.panel-group {
overflow: hidden;
width: 100%;
}
.panel-title {
font-size: 16px;
margin: 0;
}
.accordion-toggle,
a[data-toggle="collapse"] {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--mobile-spacing-md) var(--mobile-spacing-lg);
background: rgba(0,0,0,0.02);
border: 1px solid var(--kr-border-default);
border-radius: var(--mobile-radius);
cursor: pointer;
transition: all var(--transition-fast);
min-height: var(--mobile-touch-target);
text-decoration: none;
color: var(--kr-text);
font-weight: 500;
}
.accordion-toggle:hover,
a[data-toggle="collapse"]:hover {
background: rgba(139, 15, 14, 0.05);
border-color: var(--kr-primary);
color: var(--kr-primary);
}
.panel-collapse {
border: 1px solid var(--kr-border-default);
border-radius: var(--mobile-radius);
margin-top: -1px;
}
.panel-body {
padding: var(--mobile-spacing-lg);
}
}
/* ============================================================================
FORUM THREAD DETAIL - Hide tags
============================================================================ */
/* Masquer les tags dans la vue détail du fil (sujet) */
.forum-thread-detail > div:nth-of-type(2) {
display: none;
}
/* Alternative: Cibler via la structure de la page forum */
.page-forum-sujet [role="main"] > div > div:nth-of-type(2) > div:nth-of-type(3) {
display: none;
}
/* Fallback: Masquer les sections de tags générales dans les fils */
.forum-thread-metadata + div:has(a[href*="/forum/tags/"]) {
display: none;
}
/* ============================================================================
CHANGELOG MODAL
============================================================================ */
.kr-changelog-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
}
.kr-changelog-modal.active {
display: flex;
opacity: 1;
}
.kr-changelog-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75) !important; /* Fond encore plus sombre pour la lisibilité */
cursor: pointer;
}
..kr-changelog-content {
/* Positionner par rapport à la fenêtre pour garantir un centrage stable */
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
background: rgba(0, 0, 0, 0.95) !important; /* Fond noir opaque pour lisibilité */
border: 1px solid rgba(255,255,255,0.06) !important; /* bord subtil clair */
border-radius: var(--kr-radius) !important;
box-shadow: var(--kr-shadow-lg) !important;
width: 90% !important;
max-width: 600px !important;
max-height: 90vh !important;
display: flex !important;
flex-direction: column !important;
z-index: 10001 !important;
color: #ffffff !important; /* texte blanc pour contraste */
overflow: hidden !important;
}
/* Assurer le centrage parfait via flex du conteneur modal */
.kr-changelog-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.kr-changelog-header {
padding: 24px 24px 16px;
border-bottom: 1px solid var(--kr-border-default);
position: relative;
}
.kr-changelog-header h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--kr-primary);
}
/* Le bouton croix n'est plus utilisé : masquer au cas où */
.kr-changelog-close { display: none !important; }
.kr-changelog-body {
padding: 16px 24px;
overflow-y: auto;
flex: 1;
}
.kr-changelog-subtitle {
margin: 0 0 16px 0;
color: var(--kr-muted);
font-size: 14px;
}
.kr-changelog-list {
list-style: none;
padding: 0;
margin: 0;
}
.kr-changelog-list li {
padding: 8px 0 8px 24px;
position: relative;
color: var(--kr-text);
line-height: 1.5;
font-size: 14px;
}
.kr-changelog-list li:before {
content: '✓';
position: absolute;
left: 0;
color: var(--kr-primary);
font-weight: bold;
font-size: 16px;
}
.kr-changelog-footer {
padding: 16px 24px;
border-top: 1px solid var(--kr-border-default);
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.kr-changelog-footer button {
flex: 1;
min-width: 120px;
padding: 10px 16px;
border: 1px solid var(--kr-border-strong);
border-radius: var(--kr-radius);
background: var(--kr-surface);
color: var(--kr-text);
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.kr-changelog-view-all {
background: var(--kr-primary) !important;
color: white !important;
border-color: var(--kr-primary) !important;
}
.kr-changelog-view-all:hover {
background: var(--kr-primary-dark) !important;
border-color: var(--kr-primary-dark) !important;
}
.kr-changelog-close-btn:hover {
background: var(--kr-bg-hover);
}
/* Modal complet - vue historique */
.kr-changelog-modal.kr-changelog-full .kr-changelog-content-full {
max-width: 800px;
}
.kr-changelog-body-full {
max-height: 500px;
}
.kr-changelog-version {
margin-bottom: 24px;
}
.kr-changelog-version h3 {
margin: 0 0 12px 0;
font-size: 16px;
font-weight: 600;
color: var(--kr-primary);
padding-bottom: 8px;
border-bottom: 1px solid var(--kr-border-default);
}
.kr-changelog-version ul {
list-style: none;
padding: 0;
margin: 0;
}
.kr-changelog-version li {
padding: 6px 0 6px 20px;
position: relative;
color: var(--kr-text);
line-height: 1.5;
font-size: 13px;
}
.kr-changelog-version li:before {
content: '•';
position: absolute;
left: 5px;
color: var(--kr-primary);
font-weight: bold;
}
/* Bouton sur la page profil */
.kr-changelog-btn {
margin-bottom: 16px;
}
/* Style compact (inline) pour insertion avec le texte d'aide des alertes */
.kr-changelog-inline {
color: var(--kr-primary);
text-decoration: none;
border: none;
background: transparent;
padding: 0;
font-size: 13px;
margin-left: 8px;
vertical-align: middle;
cursor: pointer;
display: inline;
}
.kr-changelog-inline:hover {
color: var(--kr-primary-dark);
text-decoration: underline;
}
/* Responsive mobile */
@media (max-width: 768px) {
.kr-changelog-content {
width: 95%;
max-height: 85vh;
}
.kr-changelog-header {
padding: 16px 16px 12px;
}
.kr-changelog-header h2 {
font-size: 18px;
}
.kr-changelog-body {
padding: 12px 16px;
}
.kr-changelog-list li {
padding: 6px 0 6px 20px;
font-size: 13px;
}
.kr-changelog-footer {
padding: 12px 16px;
flex-direction: column;
}
.kr-changelog-footer button {
width: 100%;
}
.kr-changelog-content-full {
max-width: 95%;
}
}
/* ---------------------------------------------------------------------------
Corrections map : rendre les labels \`.mini\` lisibles en mode sombre
- le thème sombre utilise des fonds nationaux semi-transparents (alpha 0.15)
ce qui rend les chiffres difficiles à lire : on augmente l'opacité ici.
- on ajoute aussi un léger padding / bordure pour conserver la lisibilité
--------------------------------------------------------------------------- */
html[class*='-dark'] .mini {
color: var(--kr-text-primary) !important; /* texte clair sur fond sombre */
text-shadow: 0 1px 0 rgba(0,0,0,0.6);
padding: 2px 6px;
border-radius: 3px;
font-weight: 700;
background-image: none !important;
background-color: rgba(0,0,0,0.6) !important; /* fond opaque pour lisibilité */
}
/* conserver la couleur nationale mais en opaque pour être lisible */
html[class*='-dark'] .mini.c3 { background-color: rgba(255,255,128,0.92) !important; color: var(--kr-text-inverse) !important; }
html[class*='-dark'] .mini.c5 { background-color: rgba(128,255,128,0.92) !important; color: var(--kr-text-inverse) !important; }
/* mode clair : conserver l'apparence originale mais améliorer la lisibilité */
html:not([class*='-dark']) .mini {
padding: 2px 6px;
border-radius: 3px;
font-weight: 700;
}
`,
ENABLE_KEY: 'kr-theme-enabled',
VARIANT_KEY: 'kr-theme-variant',
STATS_DISPLAY_KEY: 'kr-stats-display',
STYLE_ID: 'kraland-theme-style',
THEME_VARIANTS: ['kraland','empire-brun','paladium','theocratie-seelienne','paradigme-vert','khanat-elmerien','confederation-libre','royaume-ruthvenie','empire-brun-dark','paladium-dark','theocratie-seelienne-dark','paradigme-vert-dark','khanat-elmerien-dark','confederation-libre-dark','royaume-ruthvenie-dark','high-contrast'],
LOGO_MAP: {
'kraland': 1, 'empire-brun': 2, 'empire-brun-dark': 2, 'paladium': 3, 'paladium-dark': 3,
'theocratie-seelienne': 4, 'theocratie-seelienne-dark': 4, 'paradigme-vert': 5, 'paradigme-vert-dark': 5,
'khanat-elmerien': 6, 'khanat-elmerien-dark': 6, 'confederation-libre': 7, 'confederation-libre-dark': 7,
'royaume-ruthvenie': 8, 'royaume-ruthvenie-dark': 8
},
SKILL_ICONS: {
'Baratin': '9401', 'Combat Mains Nues': '9402', 'Combat Contact': '9403',
'Combat Distance': '9404', 'Commerce': '9405', 'Démolition': '9406',
'Discrétion': '9407', 'Éloquence': '9408', 'Falsification': '9409',
'Foi': '9410', 'Informatique': '9411', 'Médecine': '9412',
'Observation': '9413', 'Organisation': '9414', 'Pouvoir': '9415',
'Séduction': '9416', 'Survie': '9417', 'Vol': '9418'
},
STAT_ICONS: {
'FOR': '9402', 'VOL': '9415', 'CHA': '9416',
'INT': '9412', 'GES': '9405', 'PER': '9413'
},
// Carte Médiévale
MEDIEVAL_MAP_KEY: 'kr-medieval-map',
MEDIEVAL_MAP_STYLE_ID: 'kr-medieval-map-style',
// Exceptions où l'image de remplacement ne suit pas la règle /1/ -> /5/
MEDIEVAL_MAP_OVERRIDES: {
// remplacements spécifiques (source -> cible)
'http://img7.kraland.org/2/map/1/54.gif': 'https://i.imgur.com/yQaUb2q.png',
'http://img7.kraland.org/2/map/1/123.gif': 'https://i.imgur.com/zeD5Q3m.gif',
'http://img7.kraland.org/2/map/1/124.gif': 'https://i.imgur.com/kT1NcMR.gif',
'http://img7.kraland.org/2/map/1/125.gif': 'https://i.imgur.com/OZcbEpv.gif',
'http://img7.kraland.org/2/map/1/126.gif': 'https://i.imgur.com/pnNPvQa.gif',
'http://img7.kraland.org/2/map/1/127.gif': 'https://i.imgur.com/AWDyET8.gif',
'http://img7.kraland.org/2/map/1/128.gif': 'https://i.imgur.com/v9BUsFz.gif',
'http://img7.kraland.org/2/map/1/129.gif': 'https://i.imgur.com/F3hWy6L.gif',
'http://img7.kraland.org/2/map/1/130.gif': 'https://i.imgur.com/dQ1eAgR.gif',
'http://img7.kraland.org/2/map/1/131.gif': 'https://i.imgur.com/hQzJHvq.gif',
'http://img7.kraland.org/2/map/1/150.gif': 'https://i.imgur.com/b4gCbux.png',
'http://img7.kraland.org/2/map/1/160.gif': 'https://i.imgur.com/u3pxKOO.gif',
'http://img7.kraland.org/2/map/1/161.gif': 'https://i.imgur.com/SptXelD.gif',
'http://img7.kraland.org/2/map/1/162.gif': 'https://i.imgur.com/r9AajUr.gif',
'http://img7.kraland.org/2/map/1/163.gif': 'https://i.imgur.com/j0E8T1P.gif',
'http://img7.kraland.org/2/map/1/164.gif': 'https://i.imgur.com/ogaiUqe.gif',
'http://img7.kraland.org/2/map/1/165.gif': 'https://i.imgur.com/qe3sdQF.gif',
'http://img7.kraland.org/2/map/1/166.gif': 'https://i.imgur.com/Z6QHBzh.gif',
'http://img7.kraland.org/2/map/1/167.gif': 'https://i.imgur.com/DtNeOzd.gif',
'http://img7.kraland.org/2/map/1/168.gif': 'https://i.imgur.com/bowPMN6.gif',
'http://img7.kraland.org/2/map/1/169.gif': 'https://i.imgur.com/o7CILuq.gif',
'http://img7.kraland.org/2/map/1/170.gif': 'https://i.imgur.com/y9U0dYl.gif',
// variantes 1b
'http://img7.kraland.org/2/map/1b/54.gif': 'https://i.imgur.com/06sWizC.png',
'http://img7.kraland.org/2/map/1b/123.gif': 'https://i.imgur.com/AVfs5gP.gif',
'http://img7.kraland.org/2/map/1b/124.gif': 'https://i.imgur.com/5EXjAMq.gif',
'http://img7.kraland.org/2/map/1b/125.gif': 'https://i.imgur.com/2W8B2Dt.gif',
'http://img7.kraland.org/2/map/1b/126.gif': 'https://i.imgur.com/4bBE651.gif',
'http://img7.kraland.org/2/map/1b/127.gif': 'https://i.imgur.com/MlV4iYC.gif',
'http://img7.kraland.org/2/map/1b/128.gif': 'https://i.imgur.com/rmU5dY2.gif',
'http://img7.kraland.org/2/map/1b/129.gif': 'https://i.imgur.com/YeujdJG.gif',
'http://img7.kraland.org/2/map/1b/130.gif': 'https://i.imgur.com/PqDVOX3.gif',
'http://img7.kraland.org/2/map/1b/131.gif': 'https://i.imgur.com/W8FRpdH.gif',
'http://img7.kraland.org/2/map/1b/150.gif': 'https://i.imgur.com/w6yVuNX.png',
'http://img7.kraland.org/2/map/1b/160.gif': 'https://i.imgur.com/bHKR6EJ.gif',
'http://img7.kraland.org/2/map/1b/161.gif': 'https://i.imgur.com/yrfMXwl.gif',
'http://img7.kraland.org/2/map/1b/162.gif': 'https://i.imgur.com/qfSfUkX.gif',
'http://img7.kraland.org/2/map/1b/163.gif': 'https://i.imgur.com/MR7miUh.gif',
'http://img7.kraland.org/2/map/1b/164.gif': 'https://i.imgur.com/MMunrUy.gif',
'http://img7.kraland.org/2/map/1b/165.gif': 'https://i.imgur.com/Kqs7t39.gif',
'http://img7.kraland.org/2/map/1b/166.gif': 'https://i.imgur.com/6FoA0Hi.gif',
'http://img7.kraland.org/2/map/1b/167.gif': 'https://i.imgur.com/Jgu5rX9.gif',
'http://img7.kraland.org/2/map/1b/168.gif': 'https://i.imgur.com/KCPBL3T.gif',
'http://img7.kraland.org/2/map/1b/169.gif': 'https://i.imgur.com/uBJObWh.gif',
'http://img7.kraland.org/2/map/1b/170.gif': 'https://i.imgur.com/FfpTfLi.gif'
},
MEDIEVAL_SEPIA: '85%'
};
// ============================================================================
// UTILITAIRES
// ============================================================================
/** Exécute une fonction en silence (catch les erreurs) */
function safeCall(fn) {
try { fn(); } catch (_e) { /* ignore */ }
}
/** Vérifie si le thème est activé */
function isThemeEnabled() {
return localStorage.getItem(CONFIG.ENABLE_KEY) === 'true';
}
/** Récupère la variante de thème actuelle */
function getVariant() {
return localStorage.getItem(CONFIG.VARIANT_KEY) || 'kraland';
}
/** Récupère le mode d'affichage des caractéristiques ('icon' ou 'text') */
function getStatsDisplayMode() {
return localStorage.getItem(CONFIG.STATS_DISPLAY_KEY) || 'icon';
}
/** Vérifie si la carte médiévale est activée */
function isMedievalMapEnabled() {
return localStorage.getItem(CONFIG.MEDIEVAL_MAP_KEY) === 'true';
}
// ---------------------------------------------------------------------------
// Carte médiévale (remplacement d'images via JS)
// - On n'utilise plus le CSS distant à la volée
// - Règle générale: /2/map/1/ ou /2/map/1b/ -> /2/map/5/
// - Exceptions: CONFIG.MEDIEVAL_MAP_OVERRIDES
// ---------------------------------------------------------------------------
function extractUrl(bg) {
if (!bg) return null;
const m = bg.match(/url\((?:'|")?(.*?)(?:'|")?\)/);
return m ? m[1] : null;
}
function computeReplacement(src) {
if (!src) return null;
const overrides = CONFIG.MEDIEVAL_MAP_OVERRIDES || {};
if (overrides[src]) { return overrides[src]; }
if (src.indexOf('/2/map/1b/') !== -1) {
return src.replace('/2/map/1b/', '/2/map/5/');
}
if (src.indexOf('/2/map/1/') !== -1) {
return src.replace('/2/map/1/', '/2/map/5/');
}
return null;
}
/** Applique ou retire le remplacement d'images pour la carte médiévale */
function applyMedievalMapOption() {
try {
const appliedAttr = 'data-kr-medieval-applied';
const originalBgAttr = 'data-kr-medieval-original-bg';
const originalFilterAttr = 'data-kr-medieval-original-filter';
const _krTestCache = {};
function testImageExists(url) {
if (_krTestCache.hasOwnProperty(url)) return Promise.resolve(!!_krTestCache[url]);
return new Promise(resolve => {
try {
const img = new Image();
img.onload = () => { _krTestCache[url] = true; resolve(true); };
img.onerror = () => { _krTestCache[url] = false; resolve(false); };
img.src = url;
} catch (e) { _krTestCache[url] = false; resolve(false); }
});
}
function processElement(el) {
if (!el || (el.getAttribute && el.getAttribute(appliedAttr) === 'true') || (el.getAttribute && el.getAttribute('data-kr-medieval-pending') === 'true')) return;
let src = null;
if (el.tagName === 'IMG') {
src = el.src || el.getAttribute('src');
} else {
const inlineBg = el.style && el.style.getPropertyValue('background-image') || '';
const computedBg = inlineBg || (window.getComputedStyle && getComputedStyle(el).backgroundImage) || '';
src = extractUrl(inlineBg) || extractUrl(computedBg);
}
if (!src) return;
// Ne pas remplacer les URLs explicitement exclues
if (CONFIG.MEDIEVAL_NO_REPLACE && CONFIG.MEDIEVAL_NO_REPLACE[src]) return;
const target = computeReplacement(src);
if (!target) return;
// Marquer comme pending pour éviter les re-tests simultanés
el.setAttribute && el.setAttribute('data-kr-medieval-pending', 'true');
// sauvegarde des valeurs originales
if (!el.hasAttribute || !el.hasAttribute(originalBgAttr)) {
el.setAttribute(originalBgAttr, src);
}
const origFilter = (el.style && el.style.getPropertyValue('filter')) || '';
if (!el.hasAttribute || !el.hasAttribute(originalFilterAttr)) {
el.setAttribute(originalFilterAttr, origFilter);
}
// tester l'existence de la ressource cible avant d'appliquer
testImageExists(target).then(exists => {
if (exists) {
if (el.tagName === 'IMG') {
try { el.src = target; } catch (e) { el.setAttribute('src', target); }
} else {
el.style && el.style.setProperty && el.style.setProperty('background-image', 'url("' + target + '")', 'important');
}
if (src.indexOf('/2/map/1b/') !== -1) {
el.style && el.style.setProperty && el.style.setProperty('filter', 'sepia(' + CONFIG.MEDIEVAL_SEPIA + ')', 'important');
}
el.setAttribute && el.setAttribute(appliedAttr, 'true');
} else {
// fallback: restaurer l'original si nécessaire (ne rien changer autrement)
const elemOrig = el.getAttribute(originalBgAttr);
if (el.tagName === 'IMG') {
if (elemOrig) { try { el.src = elemOrig; } catch (e) { el.setAttribute('src', elemOrig); } }
} else {
if (elemOrig) { el.style && el.style.setProperty && el.style.setProperty('background-image', 'url("' + elemOrig + '")', 'important'); } else { el.style && el.style.removeProperty && el.style.removeProperty('background-image'); }
}
const origFilterAttrVal = el.getAttribute(originalFilterAttr);
if (origFilterAttrVal) { el.style && el.style.setProperty && el.style.setProperty('filter', origFilterAttrVal, 'important'); } else { el.style && el.style.removeProperty && el.style.removeProperty('filter'); }
}
el.removeAttribute && el.removeAttribute('data-kr-medieval-pending');
}).catch(_ => { el.removeAttribute && el.removeAttribute('data-kr-medieval-pending'); });
}
function restoreAll() {
const els = document.querySelectorAll('[' + appliedAttr + '="true"]');
els.forEach(el => {
const origBg = el.getAttribute(originalBgAttr);
if (el.tagName === 'IMG') {
if (origBg) {
try { el.src = origBg; } catch (e) { el.setAttribute('src', origBg); }
} else {
el.removeAttribute && el.removeAttribute('src');
}
} else {
if (origBg) {
el.style && el.style.setProperty && el.style.setProperty('background-image', 'url("' + origBg + '")', 'important');
} else {
el.style && el.style.removeProperty && el.style.removeProperty('background-image');
}
}
const origFilter = el.getAttribute(originalFilterAttr);
if (origFilter) {
el.style && el.style.setProperty && el.style.setProperty('filter', origFilter, 'important');
} else {
el.style && el.style.removeProperty && el.style.removeProperty('filter');
}
el.removeAttribute && el.removeAttribute(appliedAttr);
el.removeAttribute && el.removeAttribute(originalBgAttr);
el.removeAttribute && el.removeAttribute(originalFilterAttr);
});
}
if (isMedievalMapEnabled()) {
document.documentElement.classList.add('kr-medieval-map-enabled');
// traitement initial des tuiles existantes (div background-image et imgs)
const candidates = document.querySelectorAll('div[style*="/2/map/1/"], div[style*="/2/map/1b/"], img[src*="/2/map/1/"], img[src*="/2/map/1b/"]');
candidates.forEach(processElement);
// observer les modifications dynamiques (nouveaux éléments ou changement d'attribut style/src)
if (!applyMedievalMapOption._observer) {
const mo = new MutationObserver(mutations => {
mutations.forEach(m => {
if (m.type === 'attributes' && (m.attributeName === 'style' || m.attributeName === 'src') && m.target && m.target.nodeType === 1) {
processElement(m.target);
} else if (m.type === 'childList') {
m.addedNodes.forEach(n => {
if (n.nodeType !== 1) return;
if (n.matches && (n.matches('div[style*="/2/map/1/"]') || n.matches('div[style*="/2/map/1b/"]') || n.matches('img[src*="/2/map/1/"]') || n.matches('img[src*="/2/map/1b/"]'))) {
processElement(n);
}
n.querySelectorAll && n.querySelectorAll('div[style*="/2/map/1/"], div[style*="/2/map/1b/"], img[src*="/2/map/1/"], img[src*="/2/map/1b/"]').forEach(processElement);
});
}
});
});
mo.observe(document.body || document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['style','src'] });
applyMedievalMapOption._observer = mo;
}
} else {
document.documentElement.classList.remove('kr-medieval-map-enabled');
restoreAll();
if (applyMedievalMapOption._observer) {
applyMedievalMapOption._observer.disconnect();
applyMedievalMapOption._observer = null;
}
}
return true;
} catch (e) {
console.error('applyMedievalMapOption error', e);
return false;
}
}
/** Vérifie si on est sur la page /jouer */
function isPlatoPage() {
const path = location?.pathname || '';
return path.indexOf('/jouer') === 0 &&
path !== '/jouer/communaute' &&
path !== '/jouer/communaute/membres';
}
/** Crée un badge numérique pour les icônes de compétences (rouge, à droite) */
function createBadge(text) {
const badge = document.createElement('span');
badge.className = 'badge';
badge.textContent = text;
Object.assign(badge.style, {
position: 'absolute', top: '25px', right: '-8px',
backgroundColor: '#d9534f', color: '#fff',
borderRadius: '50%', width: '19px', height: '19px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '11px', fontWeight: 'bold', border: '2px solid #fff'
});
return badge;
}
/** Crée un badge numérique pour les caractéristiques (bleu, en haut à gauche) */
function createStatBadge(text) {
const badge = document.createElement('span');
badge.className = 'badge';
badge.textContent = text;
Object.assign(badge.style, {
position: 'absolute', top: '25px', right: '-8px',
backgroundColor: '#007bff', color: '#fff',
borderRadius: '50%', width: '19px', height: '19px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '11px', fontWeight: 'bold', border: '2px solid #fff'
});
return badge;
}
/** Crée un conteneur d'icône avec badge */
function createIconContainer(iconUrl, altText, badgeText) {
const container = document.createElement('div');
Object.assign(container.style, {
position: 'relative', display: 'inline-block',
width: '32px', height: '32px'
});
const img = document.createElement('img');
img.src = iconUrl;
img.alt = altText;
img.title = altText;
Object.assign(img.style, { width: '32px', height: '32px', display: 'block' });
container.appendChild(img);
container.appendChild(createBadge(badgeText));
return container;
}
/** Crée un conteneur d'icône avec badge pour caractéristiques */
function _createStatIconContainer(iconUrl, altText, badgeText) {
const container = document.createElement('div');
Object.assign(container.style, {
position: 'relative', display: 'inline-block',
width: '32px', height: '32px'
});
const img = document.createElement('img');
img.src = iconUrl;
img.alt = altText;
img.title = altText;
Object.assign(img.style, { width: '32px', height: '32px', display: 'block' });
container.appendChild(img);
container.appendChild(createStatBadge(badgeText));
return container;
}
// ============================================================================
// INJECTION CSS IMMÉDIATE (avant tout code async)
// ============================================================================
(function injectCSSImmediately(){
try {
if (!isThemeEnabled()) {return;}
const st = document.createElement('style');
st.id = CONFIG.STYLE_ID;
st.textContent = CONFIG.BUNDLED_CSS;
(document.head || document.documentElement).appendChild(st);
const variant = getVariant();
document.documentElement.classList.add('kr-theme-enabled', 'kr-theme-variant-' + variant);
if (variant === 'high-contrast') {
document.documentElement.classList.add('kr-theme-high-contrast');
}
} catch(e) { console.error('CSS injection failed', e); }
})();
// Appliquer la carte médiévale si activée (asynchrone, non bloquant)
safeCall(() => applyMedievalMapOption());
// ============================================================================
// GESTION DU THÈME
// ============================================================================
async function applyThemeInline(cssText) {
if (!isThemeEnabled()) {return false;}
try {
let st = document.getElementById(CONFIG.STYLE_ID);
if (st) {
st.textContent = cssText;
} else {
st = document.createElement('style');
st.id = CONFIG.STYLE_ID;
st.textContent = cssText;
document.head.appendChild(st);
}
document.documentElement.classList.add('kr-theme-enabled');
const variant = getVariant();
// High contrast
document.documentElement.classList.toggle('kr-theme-high-contrast', variant === 'high-contrast');
// Variant classes
CONFIG.THEME_VARIANTS.forEach(v =>
document.documentElement.classList.remove('kr-theme-variant-' + v)
);
if (variant && variant !== 'disable') {
document.documentElement.classList.add('kr-theme-variant-' + variant);
}
// Page members
const isMembers = location?.pathname?.indexOf('/communaute/membres') === 0;
document.documentElement.classList.toggle('kr-page-members', isMembers);
return true;
} catch(e) {
console.error('Theme apply failed', e);
return false;
}
}
async function ensureTheme() {
if (!isThemeEnabled()) {return;}
await applyThemeInline(CONFIG.BUNDLED_CSS);
}
function applyThemeVariant(variant, skipReload = false) {
try {
if (!variant || variant === 'disable') {
localStorage.setItem(CONFIG.ENABLE_KEY, 'false');
if (!skipReload) {location.reload();}
return;
}
const wasDisabled = !isThemeEnabled();
localStorage.setItem(CONFIG.ENABLE_KEY, 'true');
localStorage.setItem(CONFIG.VARIANT_KEY, variant);
if (wasDisabled && !skipReload) {
location.reload();
return;
}
// Switch variant sans reload
CONFIG.THEME_VARIANTS.forEach(v =>
document.documentElement.classList.remove('kr-theme-variant-' + v)
);
document.documentElement.classList.add('kr-theme-variant-' + variant);
document.documentElement.classList.add('kr-theme-enabled');
safeCall(() => applyThemeInline(CONFIG.BUNDLED_CSS));
safeCall(() => replaceNavbarBrand());
} catch(e) { console.error('applyThemeVariant error', e); }
}
function getThemeState() {
if (localStorage.getItem(CONFIG.ENABLE_KEY) === null) {
localStorage.setItem(CONFIG.ENABLE_KEY, 'false');
}
return isThemeEnabled();
}
// ============================================================================
// TRANSFORMATIONS DOM
// ============================================================================
/**
* Applique l'option de masquage de la citation du footer
*/
function applyFooterQuoteOption() {
const hideQuote = localStorage.getItem('kr-hide-footer-quote') === 'true';
if (hideQuote) {
document.documentElement.classList.add('kr-hide-footer-quote');
} else {
document.documentElement.classList.remove('kr-hide-footer-quote');
}
}
function applyDOMTransformations() {
if (!isThemeEnabled()) {return;}
const transforms = [
markActiveIcons, replaceMcAnchors, replaceSImages, replaceNavbarBrand,
reorderBtnGroupXs, ensureSexStrong, ensureFooterSticky, displayVersionInfo,
relocateKramailToLeft, restructurePlatoColumns, moveBtnGroupToCols, moveSkillsPanelToCols,
transformToBootstrapGrid, nameLeftSidebarDivs, transformSkillsToIcons,
transformStatsToNotifications, ensureEditorClasses, ensurePageScoping,
ensurePlayerMainPanelRows, addQuickAccessButtons, addRankTitles,
disableTooltips, modifyNavigationMenus, window.updateForumRPMenu,
window.updateForumHRPMenu, window.updateForumCommunauteMenu,
window.updateForumDebatsMenu, window.updateForumStaffMenu,
transformDashboardToFlexCards, applyFooterQuoteOption, handleDualLapClock
];
transforms.forEach(fn => safeCall(fn));
}
function disableTooltips() {
// Désactiver les tooltips:
// 1. Sur mobile
// 2. Si enfant de col-leftest ou panel-body grid-transformed
document.querySelectorAll('[data-toggle="tooltip"]').forEach(el => {
const isMobile = document.body.classList.contains('mobile-mode');
const isInColLeftest = el.closest('.col-leftest');
const isInGridTransformed = el.closest('.panel-body.grid-transformed');
if (isMobile || isInColLeftest || isInGridTransformed) {
el.removeAttribute('data-toggle');
el.removeAttribute('data-placement');
el.removeAttribute('title');
el.removeAttribute('data-original-title');
}
});
if (window.$ && window.$.fn && window.$.fn.tooltip) {
window.$.fn.tooltip = function () { return this; };
}
}
/**
* Ajoute les titres des rangs du forum dans des divs soeurs
*/
function addRankTitles() {
// Ne s'exécuter que sur les pages du forum
if (!window.location.pathname.startsWith('/forum/')) {return;}
// Trouver toutes les images de rang
document.querySelectorAll('img[src*="img7.kraland.org/2/rank/"]').forEach(img => {
// Récupérer le contenu de data-original-title ou title
let title = img.getAttribute('data-original-title') || img.getAttribute('title');
if (!title) {return;}
// Trouver la div parente contenant l'image
let parentDiv = img.closest('div');
if (!parentDiv || !parentDiv.parentElement) {return;}
// Chercher une qui contient un lien avec "charlie-2-82045"
// La strong devrait être dans le même cartouche (div parente du parent)
let cartouche = parentDiv.closest('.cartouche') || parentDiv.closest('div[class="cartouche"]');
if (!cartouche) {
cartouche = parentDiv.parentElement;
}
if (cartouche) {
const strongWithLink = cartouche.querySelector('strong a[href*="charlie-2-82045"]');
if (strongWithLink) {
// C'est Charlie (ou un compte avec charlie-2-82045), remplacer "Empereur" par "Emperatrice"
if (title === 'Empereur') {
title = 'Emperatrice';
}
}
}
// Remplacements pour Mystisie
if (title === 'Gouverneure Mystisie') {
title = 'Sultane Eternelle de Mystisie';
} else if (title === 'Gouverneur Mystisie') {
title = 'Sultan Eternel de Mystisie';
}
// Vérifier si une div soeur avec ce titre existe déjà (pour éviter les doublons)
const nextSiblings = parentDiv.parentElement.querySelectorAll('div');
let titleAlreadyExists = false;
for (let sibling of nextSiblings) {
if (sibling.textContent.trim() === title && sibling !== parentDiv) {
titleAlreadyExists = true;
break;
}
}
// Si le titre existe déjà, ne rien faire
if (titleAlreadyExists) {return;}
// Vérifier aussi si on a déjà créé cette div (avec le data-kr-rank-title)
const nextSibling = parentDiv.nextElementSibling;
if (nextSibling && nextSibling.hasAttribute('data-kr-rank-title')) {return;}
// Créer un conteneur flex pour l'icône + titre
const rankContainer = document.createElement('div');
rankContainer.setAttribute('data-kr-rank-title', 'true');
rankContainer.style.display = 'flex';
rankContainer.style.alignItems = 'center';
rankContainer.style.justifyContent = 'center';
rankContainer.style.gap = '4px';
rankContainer.style.marginTop = '4px';
// Déplacer l'image dans ce conteneur
const rankImg = parentDiv.querySelector('img[src*="/rank/"]');
if (rankImg) {
rankContainer.appendChild(rankImg.cloneNode(true));
}
// Ajouter le titre
const strong = document.createElement('strong');
strong.textContent = title;
rankContainer.appendChild(strong);
// Remplacer la div parente par le nouveau conteneur
if (parentDiv.parentElement) {
parentDiv.parentElement.replaceChild(rankContainer, parentDiv);
}
});
}
/**
* Extrait les données d'un membre depuis son élément DOM
*/
/**
* Extrait les données d'un groupe complet (boutons de groupe + membres avec actions)
*/
function extractGroupData(panel) {
const groupData = {
title: '',
groupButtons: [],
members: []
};
// Extraire le titre et les boutons du panel-heading
const panelHeading = panel.querySelector('.panel-heading');
if (panelHeading) {
const panelTitle = panelHeading.querySelector('.panel-title');
if (panelTitle) {
// Extraire le texte du titre (sans les boutons)
groupData.title = panelTitle.textContent.trim();
// Extraire les boutons de groupe (avec cloneNode pour préserver événements)
const buttons = panelTitle.querySelectorAll('a.btn');
buttons.forEach(btn => {
groupData.groupButtons.push(btn.cloneNode(true));
});
}
}
// Extraire les membres et leurs actions individuelles
const panelBody = panel.querySelector('.panel-body');
if (!panelBody) {return groupData;}
const table = panelBody.querySelector('table');
if (!table) {return groupData;}
const rows = table.querySelectorAll('tr');
rows.forEach(row => {
const td1 = row.querySelector('td:first-child');
const td2 = row.querySelector('td:last-child');
if (!td1 || !td2) {return;}
// Récupérer les liens membres (TD1)
const memberLinks = td1.querySelectorAll('a.list-group-item.ds_game');
// Récupérer les divs d'actions (TD2) - une div de 59px par personnage
const actionsDivs = td2.querySelectorAll('div[style*="height:59px"]');
// Associer chaque membre à sa div d'actions par index
memberLinks.forEach((memberLink, index) => {
const actionsDiv = actionsDivs[index] || null;
const memberData = extractMemberData(memberLink, actionsDiv);
groupData.members.push(memberData);
});
});
return groupData;
}
/**
* Extrait les données d'un membre individuel
*/
function extractMemberData(memberLink, actionsDiv) {
const data = {
originalLink: memberLink, // Garder le lien original pour préserver classes et événements
avatar: null,
name: '',
status: '',
isPNJ: false,
worldImage: null,
hpInfo: null,
pvLevel: null, // Niveau de PV de 1 à 5 (pdv1.png à pdv5.png)
profileUrl: '',
actionsDiv: null // Div contenant les actions de CE personnage uniquement
};
// Avatar
const avatar = memberLink.querySelector('img.pull-left');
if (avatar) {
data.avatar = avatar.src;
}
// Nom et statut
const heading = memberLink.querySelector('.list-group-item-heading');
const text = memberLink.querySelector('.list-group-item-text');
if (heading) {
data.name = heading.textContent.trim();
}
if (text) {
data.status = text.textContent.trim();
}
// PNJ button
const pnjButton = memberLink.querySelector('.btn-danger.xmini');
if (pnjButton) {
data.isPNJ = true;
}
// Icône de monde et HP
const mention = memberLink.querySelector('.mention.pull-right');
if (mention) {
const worldImg = mention.querySelector('img[src*="world"]');
if (worldImg) {
data.worldImage = worldImg.src;
}
// Extraire le niveau de PV depuis l'image pdv1.png à pdv5.png
const pvImg = mention.querySelector('img[src*="pdv"]');
if (pvImg) {
const match = pvImg.src.match(/pdv(\d)\.png/);
if (match) {
data.pvLevel = parseInt(match[1], 10); // 1 à 5
}
}
// Extraire les HP depuis l'image de barre (fallback)
const hpDiv = mention.querySelector('div[style*="background"]');
if (hpDiv) {
const style = hpDiv.getAttribute('style') || '';
const match = style.match(/width:\s*(\d+)%/);
if (match) {
data.hpInfo = parseInt(match[1], 10);
}
}
}
// URL du profil
data.profileUrl = memberLink.href;
// Cloner la div d'actions de CE personnage uniquement
if (actionsDiv) {
data.actionsDiv = actionsDiv.cloneNode(true);
}
return data;
}
/**
* Crée un cercle SVG de progression pour les PV
*/
function createHPCircle(pvLevel) {
if (!pvLevel) {return null;}
// Correspondance niveau PV -> couleur et pourcentage
// pdv1 = 100% (pleine santé), pdv5 = 20% (presque mort)
const pvConfig = {
1: { color: '#32CD32', percentage: 100 }, // Vert lime - Pleine santé
2: { color: '#FFD700', percentage: 80 }, // Jaune/or
3: { color: '#FF8C00', percentage: 60 }, // Orange foncé
4: { color: '#DC143C', percentage: 40 }, // Rouge crimson
5: { color: '#8B0000', percentage: 20 } // Rouge foncé - Presque mort
};
const config = pvConfig[pvLevel] || pvConfig[1];
const radius = 37; // Rayon pour un avatar de 70px (35px) + bordure
const circumference = 2 * Math.PI * radius;
const strokeDashoffset = circumference - (circumference * config.percentage / 100);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('class', 'dashboard-card-hp-circle');
svg.setAttribute('width', '82');
svg.setAttribute('height', '82');
svg.setAttribute('viewBox', '0 0 82 82');
// Cercle de fond (gris)
const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
bgCircle.setAttribute('cx', '41');
bgCircle.setAttribute('cy', '41');
bgCircle.setAttribute('r', radius);
bgCircle.setAttribute('fill', 'none');
bgCircle.setAttribute('stroke', 'rgba(0,0,0,0.1)');
bgCircle.setAttribute('stroke-width', '3');
// Cercle de progression (coloré)
const progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
progressCircle.setAttribute('cx', '41');
progressCircle.setAttribute('cy', '41');
progressCircle.setAttribute('r', radius);
progressCircle.setAttribute('fill', 'none');
progressCircle.setAttribute('stroke', config.color);
progressCircle.setAttribute('stroke-width', '4');
progressCircle.setAttribute('stroke-linecap', 'round');
progressCircle.setAttribute('stroke-dasharray', circumference);
progressCircle.setAttribute('stroke-dashoffset', strokeDashoffset);
progressCircle.setAttribute('transform', 'rotate(-90 41 41)');
svg.appendChild(bgCircle);
svg.appendChild(progressCircle);
return svg;
}
/**
* Crée une card pour un membre
*/
function buildCard(memberData, isLargeCard = false) {
const card = document.createElement('div');
card.className = isLargeCard ? 'dashboard-card dashboard-card-large' : 'dashboard-card';
// Cloner le lien original pour préserver toutes les classes et événements
const cardLink = memberData.originalLink.cloneNode(false);
cardLink.classList.add('dashboard-card-link');
// Vider le contenu pour reconstruire
// Header avec avatar, drapeau et nom
const header = document.createElement('div');
header.className = 'dashboard-card-header';
if (memberData.avatar) {
// Créer un wrapper pour l'avatar avec le cercle de PV
const avatarWrapper = document.createElement('div');
avatarWrapper.className = 'dashboard-card-avatar-wrapper';
// Ajouter le cercle SVG si on a l'info des PV
if (memberData.pvLevel) {
const hpCircle = createHPCircle(memberData.pvLevel);
if (hpCircle) {
avatarWrapper.appendChild(hpCircle);
}
}
// Ajouter l'avatar
const avatarImg = document.createElement('img');
avatarImg.src = memberData.avatar;
avatarImg.className = 'dashboard-card-avatar';
avatarImg.alt = memberData.name;
// Appliquer un filtre gris si le personnage est KO (pdv5)
if (memberData.pvLevel === 5) {
avatarImg.style.filter = 'grayscale(100%)';
avatarImg.style.opacity = '0.7';
}
avatarWrapper.appendChild(avatarImg);
header.appendChild(avatarWrapper);
}
// Conteneur pour le drapeau et le nom
const nameContainer = document.createElement('div');
nameContainer.className = 'dashboard-card-name-container';
if (memberData.worldImage) {
const worldImg = document.createElement('img');
worldImg.src = memberData.worldImage;
worldImg.className = 'dashboard-card-world';
worldImg.alt = 'World';
nameContainer.appendChild(worldImg);
}
const nameDiv = document.createElement('div');
nameDiv.className = 'dashboard-card-name';
nameDiv.textContent = memberData.name;
nameContainer.appendChild(nameDiv);
header.appendChild(nameContainer);
cardLink.appendChild(header);
// Body avec statut uniquement (pas de monde, il sera dans les actions)
const body = document.createElement('div');
body.className = 'dashboard-card-body';
if (memberData.status) {
const statusDiv = document.createElement('div');
statusDiv.className = 'dashboard-card-status';
statusDiv.textContent = memberData.status;
body.appendChild(statusDiv);
}
cardLink.appendChild(body);
card.appendChild(cardLink);
// Barre de HP
if (memberData.hpInfo !== null) {
const hpBar = document.createElement('div');
hpBar.className = 'dashboard-card-hp';
const hpFill = document.createElement('div');
hpFill.className = 'dashboard-card-hp-fill';
hpFill.style.width = memberData.hpInfo + '%';
// Couleur selon le pourcentage
if (memberData.hpInfo > 70) {
hpFill.style.backgroundColor = '#5cb85c'; // vert
} else if (memberData.hpInfo > 30) {
hpFill.style.backgroundColor = '#f0ad4e'; // jaune
} else {
hpFill.style.backgroundColor = '#d9534f'; // rouge
}
hpBar.appendChild(hpFill);
card.appendChild(hpBar);
}
// Bouton PNJ
if (memberData.isPNJ) {
const pnjBadge = document.createElement('span');
pnjBadge.className = 'dashboard-card-pnj';
pnjBadge.textContent = 'PNJ';
card.appendChild(pnjBadge);
}
// Actions individuelles de CE personnage
if (memberData.actionsDiv) {
const actionsWrapper = document.createElement('div');
actionsWrapper.className = 'dashboard-card-actions';
// Extraire les liens d'action individuels (ignorer les divs conteneurs)
const actionLinks = memberData.actionsDiv.querySelectorAll('a');
actionLinks.forEach(link => {
actionsWrapper.appendChild(link.cloneNode(true));
});
card.appendChild(actionsWrapper);
}
return card;
}
/**
* Transforme le dashboard en système de flex cards
*/
function transformDashboardToFlexCards() {
if (!isPlatoPage()) {return;}
const dashboard = document.querySelector('.dashboard');
if (!dashboard) {return;}
const panels = dashboard.querySelectorAll(':scope > .panel');
if (!panels.length) {return;}
// Créer le nouveau conteneur flex
const flexContainer = document.createElement('div');
flexContainer.className = 'dashboard-flex';
// Tableau de groupes avec leurs données complètes
const groups = [];
let firstPlayerPanelFound = false;
// Extraire toutes les données par groupe
panels.forEach(panel => {
const panelBody = panel.querySelector('.panel-body');
if (!panelBody) {return;}
const table = panelBody.querySelector('table');
if (!table) {return;}
// Vérifier si c'est un panel de groupe de personnages (titre contient "Groupe")
const panelTitle = panel.querySelector('.panel-heading .panel-title');
const titleText = panelTitle ? panelTitle.textContent.trim() : '';
if (!titleText.toLowerCase().includes('groupe')) {return;}
// Extraire toutes les données du groupe (titre, boutons, membres)
const groupData = extractGroupData(panel);
if (groupData.members.length === 0) {return;}
// Le premier panel avec des personnages = Mon groupe
const isMyGroup = !firstPlayerPanelFound;
if (titleText.toLowerCase().includes('groupe')) {firstPlayerPanelFound = true;}
groups.push({
isMyGroup: isMyGroup,
title: groupData.title,
groupButtons: groupData.groupButtons,
members: groupData.members
});
});
// Ne transformer que si on a trouvé au moins un groupe
if (groups.length === 0) {return;}
// Construire les sections pour chaque groupe
groups.forEach((group, _index) => {
const groupSection = document.createElement('div');
groupSection.className = group.isMyGroup
? 'dashboard-section dashboard-section-mygroup'
: 'dashboard-section dashboard-section-others';
// En-tête de section avec titre et boutons de groupe
const header = document.createElement('div');
header.className = 'dashboard-section-header';
// Ajouter les boutons de groupe en premier
if (group.groupButtons && group.groupButtons.length > 0) {
const buttonsWrapper = document.createElement('div');
buttonsWrapper.className = 'dashboard-group-buttons';
group.groupButtons.forEach(btn => {
buttonsWrapper.appendChild(btn);
});
header.appendChild(buttonsWrapper);
}
// Ajouter le titre du groupe
const titleSpan = document.createElement('span');
titleSpan.className = 'dashboard-group-title';
if (group.isMyGroup) {
// Extraire le nom sans les icônes
const titleText = group.title.replace(/\s*Groupe\s+/i, '');
titleSpan.textContent = titleText || 'Mon groupe';
} else {
// Extraire le nom du groupe
const titleText = group.title.replace(/\s*Groupe\s+/i, '');
titleSpan.textContent = titleText || `Groupe de ${group.members[0]?.name || 'Inconnu'}`;
}
header.appendChild(titleSpan);
groupSection.appendChild(header);
// Grille des cartes
const cardsContainer = document.createElement('div');
cardsContainer.className = group.isMyGroup
? 'dashboard-cards-grid dashboard-cards-large'
: 'dashboard-cards-grid';
group.members.forEach(member => {
cardsContainer.appendChild(buildCard(member, group.isMyGroup));
});
groupSection.appendChild(cardsContainer);
flexContainer.appendChild(groupSection);
});
// Remplacer le contenu du dashboard
dashboard.innerHTML = '';
dashboard.appendChild(flexContainer);
}
// ============================================
// DYNAMIC FORUM RP MENU FROM PAGE CONTENT
// ============================================
(function () {
'use strict';
const STORAGE_KEY = 'kr-forums-rp';
const EXCLUDED_FORUMS = ['taverne', 'marché', 'monde', 'communauté', 'débats', 'staff'];
/**
* Extrait les forums de la page /forum/rp et les stocke dans localStorage
* Exclut Taverne, Marché, Monde, Communauté, Débats et Staff
*/
function extractAndStoreForumsRP() {
// Ne s'exécuter que sur /forum/rp
if (window.location.pathname !== '/forum/rp') {
return;
}
console.log('[Forums RP] Extraction des forums depuis la page...');
const forums = [];
// Sélectionner toutes les lignes du tableau des forums
const forumRows = document.querySelectorAll('table.table tbody tr');
forumRows.forEach(row => {
const linkCell = row.querySelector('td:first-child a');
if (!linkCell) {return;}
const forumName = linkCell.textContent.trim();
const forumUrl = linkCell.getAttribute('href');
// Exclure les forums de la liste noire (insensible à la casse)
const isExcluded = EXCLUDED_FORUMS.some(excluded =>
forumName.toLowerCase().includes(excluded)
);
if (!isExcluded && forumUrl) {
forums.push({
name: forumName,
url: forumUrl
});
console.log(`[Forums RP] Ajouté: ${forumName} (${forumUrl})`);
} else {
console.log(`[Forums RP] Exclu: ${forumName}`);
}
});
// Stocker dans localStorage
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(forums));
console.log(`[Forums RP] ${forums.length} forums stockés dans localStorage`);
} catch (e) {
console.error('[Forums RP] Erreur sauvegarde localStorage:', e);
}
}
/**
* Récupère les forums RP depuis le localStorage
*/
function getStoredForumsRP() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Forums RP] Erreur lecture localStorage:', e);
return [];
}
}
/**
* Met à jour le menu Forum RP avec les forums stockés
*/
window.updateForumRPMenu = function () {
const forumRpDropdown = document.querySelector('[data-forums-added="rp"] .dropdown-menu');
if (!forumRpDropdown) {
// Menu pas encore créé ou page sans navigation - normal
return;
}
const forums = getStoredForumsRP();
console.log(`[Forums RP] Mise à jour du menu avec ${forums.length} forums`);
// Conserver les 3 premiers liens (Taverne, Marché, Monde) et le divider
const staticItems = Array.from(forumRpDropdown.children).slice(0, 4); // 3 liens + 1 divider
// Vider le menu
forumRpDropdown.innerHTML = '';
// Remettre les items statiques
staticItems.forEach(item => forumRpDropdown.appendChild(item));
// Ajouter les forums dynamiques
if (forums.length > 0) {
forums.forEach(forum => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = forum.url;
a.textContent = forum.name;
li.appendChild(a);
forumRpDropdown.appendChild(li);
});
} else {
// Si aucun forum stocké, afficher un lien vers la page /forum/rp
const li = document.createElement('li');
const a = document.createElement('a');
a.href = 'forum/rp';
a.textContent = 'Autre';
li.appendChild(a);
forumRpDropdown.appendChild(li);
}
};
// Enregistrer l'extraction dans InitQueue (priorité haute pour s'exécuter dès que possible)
InitQueue.register('ForumsRP:Extract', extractAndStoreForumsRP, 5);
})();
// ============================================
// DYNAMIC FORUM HRP MENU FROM PAGE CONTENT
// ============================================
(function () {
'use strict';
const STORAGE_KEY = 'kr-forums-hrp';
/**
* Extrait les forums de la page /forum/hrp et les stocke dans localStorage
*/
function extractAndStoreForumsHRP() {
// Ne s'exécuter que sur /forum/hrp
if (window.location.pathname !== '/forum/hrp') {
return;
}
console.log('[Forums HRP] Extraction des forums depuis la page...');
const forums = [];
// Sélectionner toutes les lignes du tableau des forums
const forumRows = document.querySelectorAll('table.table tbody tr');
forumRows.forEach(row => {
const linkCell = row.querySelector('td:first-child a');
if (!linkCell) {return;}
const forumName = linkCell.textContent.trim();
const forumUrl = linkCell.getAttribute('href');
if (forumUrl) {
forums.push({
name: forumName,
url: forumUrl
});
console.log(`[Forums HRP] Ajouté: ${forumName} (${forumUrl})`);
}
});
// Stocker dans localStorage
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(forums));
console.log(`[Forums HRP] ${forums.length} forums stockés dans localStorage`);
} catch (e) {
console.error('[Forums HRP] Erreur sauvegarde localStorage:', e);
}
}
/**
* Récupère les forums HRP depuis le localStorage
*/
function getStoredForumsHRP() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Forums HRP] Erreur lecture localStorage:', e);
return [];
}
}
/**
* Met à jour le menu Forum HRP avec les forums stockés
*/
window.updateForumHRPMenu = function () {
const forumHrpDropdown = document.querySelector('[data-forums-added="hrp"] .dropdown-menu');
if (!forumHrpDropdown) {
// Menu pas encore créé ou page sans navigation - normal
return;
}
const forums = getStoredForumsHRP();
console.log(`[Forums HRP] Mise à jour du menu avec ${forums.length} forums`);
// Vider le menu
forumHrpDropdown.innerHTML = '';
// Ajouter tous les forums dynamiques
if (forums.length > 0) {
forums.forEach(forum => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = forum.url;
a.textContent = forum.name;
li.appendChild(a);
forumHrpDropdown.appendChild(li);
});
} else {
// Si aucun forum stocké, afficher un lien vers la page /forum/hrp
const li = document.createElement('li');
const a = document.createElement('a');
a.href = 'forum/hrp';
a.textContent = 'Voir tous les forums';
li.appendChild(a);
forumHrpDropdown.appendChild(li);
}
};
// Enregistrer l'extraction dans InitQueue (priorité haute pour s'exécuter dès que possible)
InitQueue.register('ForumsHRP:Extract', extractAndStoreForumsHRP, 5);
})();
// ============================================
// DYNAMIC FORUM COMMUNAUTE MENU FROM PAGE CONTENT
// ============================================
(function () {
'use strict';
const STORAGE_KEY = 'kr-forums-communaute';
const EXCLUDED_FORUMS = ['taverne', 'marché', 'monde', 'communauté', 'débats', 'staff'];
function extractAndStoreForumsCommunaute() {
if (window.location.pathname !== '/forum/communaute') {
return;
}
console.log('[Forums Communauté] Extraction des forums depuis la page...');
const forums = [];
const forumRows = document.querySelectorAll('table.table tbody tr');
forumRows.forEach(row => {
const linkCell = row.querySelector('td:first-child a');
if (!linkCell) {return;}
const forumName = linkCell.textContent.trim();
const forumUrl = linkCell.getAttribute('href');
const isExcluded = EXCLUDED_FORUMS.some(excluded =>
forumName.toLowerCase().includes(excluded)
);
if (!isExcluded && forumUrl) {
forums.push({
name: forumName,
url: forumUrl
});
console.log(`[Forums Communauté] Ajouté: ${forumName} (${forumUrl})`);
}
});
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(forums));
console.log(`[Forums Communauté] ${forums.length} forums stockés dans localStorage`);
} catch (e) {
console.error('[Forums Communauté] Erreur sauvegarde localStorage:', e);
}
}
function getStoredForumsCommunaute() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Forums Communauté] Erreur lecture localStorage:', e);
return [];
}
}
window.updateForumCommunauteMenu = function () {
const forumCommunauteDropdown = document.querySelector('[data-forums-added="forum-communaute"] .dropdown-menu');
if (!forumCommunauteDropdown) {
return;
}
const forums = getStoredForumsCommunaute();
console.log(`[Forums Communauté] Mise à jour du menu avec ${forums.length} forums`);
forumCommunauteDropdown.innerHTML = '';
if (forums.length > 0) {
forums.forEach(forum => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = forum.url;
a.textContent = forum.name;
li.appendChild(a);
forumCommunauteDropdown.appendChild(li);
});
} else {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = 'forum/communaute';
a.textContent = 'Voir tous les forums';
li.appendChild(a);
forumCommunauteDropdown.appendChild(li);
}
};
InitQueue.register('ForumsCommunaute:Extract', extractAndStoreForumsCommunaute, 5);
})();
// ============================================
// DYNAMIC FORUM DEBATS MENU FROM PAGE CONTENT
// ============================================
(function () {
'use strict';
const STORAGE_KEY = 'kr-forums-debats';
const EXCLUDED_FORUMS = ['taverne', 'marché', 'monde', 'communauté', 'débats', 'staff'];
function extractAndStoreForumsDebats() {
if (window.location.pathname !== '/forum/debats') {
return;
}
console.log('[Forums Débats] Extraction des forums depuis la page...');
const forums = [];
const forumRows = document.querySelectorAll('table.table tbody tr');
forumRows.forEach(row => {
const linkCell = row.querySelector('td:first-child a');
if (!linkCell) {return;}
const forumName = linkCell.textContent.trim();
const forumUrl = linkCell.getAttribute('href');
const isExcluded = EXCLUDED_FORUMS.some(excluded =>
forumName.toLowerCase().includes(excluded)
);
if (!isExcluded && forumUrl) {
forums.push({
name: forumName,
url: forumUrl
});
console.log(`[Forums Débats] Ajouté: ${forumName} (${forumUrl})`);
}
});
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(forums));
console.log(`[Forums Débats] ${forums.length} forums stockés dans localStorage`);
} catch (e) {
console.error('[Forums Débats] Erreur sauvegarde localStorage:', e);
}
}
function getStoredForumsDebats() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Forums Débats] Erreur lecture localStorage:', e);
return [];
}
}
window.updateForumDebatsMenu = function () {
const forumDebatsDropdown = document.querySelector('[data-forums-added="forum-debats"] .dropdown-menu');
if (!forumDebatsDropdown) {
return;
}
const forums = getStoredForumsDebats();
console.log(`[Forums Débats] Mise à jour du menu avec ${forums.length} forums`);
forumDebatsDropdown.innerHTML = '';
if (forums.length > 0) {
forums.forEach(forum => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = forum.url;
a.textContent = forum.name;
li.appendChild(a);
forumDebatsDropdown.appendChild(li);
});
} else {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = 'forum/debats';
a.textContent = 'Voir tous les forums';
li.appendChild(a);
forumDebatsDropdown.appendChild(li);
}
};
InitQueue.register('ForumsDebats:Extract', extractAndStoreForumsDebats, 5);
})();
// ============================================
// DYNAMIC FORUM STAFF MENU FROM PAGE CONTENT
// ============================================
(function () {
'use strict';
const STORAGE_KEY = 'kr-forums-staff';
const EXCLUDED_FORUMS = ['taverne', 'marché', 'monde', 'communauté', 'débats', 'staff'];
function extractAndStoreForumsStaff() {
if (window.location.pathname !== '/forum/staff') {
return;
}
console.log('[Forums Staff] Extraction des forums depuis la page...');
const forums = [];
const forumRows = document.querySelectorAll('table.table tbody tr');
forumRows.forEach(row => {
const linkCell = row.querySelector('td:first-child a');
if (!linkCell) {return;}
const forumName = linkCell.textContent.trim();
const forumUrl = linkCell.getAttribute('href');
const isExcluded = EXCLUDED_FORUMS.some(excluded =>
forumName.toLowerCase().includes(excluded)
);
if (!isExcluded && forumUrl) {
forums.push({
name: forumName,
url: forumUrl
});
console.log(`[Forums Staff] Ajouté: ${forumName} (${forumUrl})`);
}
});
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(forums));
console.log(`[Forums Staff] ${forums.length} forums stockés dans localStorage`);
} catch (e) {
console.error('[Forums Staff] Erreur sauvegarde localStorage:', e);
}
}
function getStoredForumsStaff() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (e) {
console.warn('[Forums Staff] Erreur lecture localStorage:', e);
return [];
}
}
window.updateForumStaffMenu = function () {
const forumStaffDropdown = document.querySelector('[data-forums-added="forum-staff"] .dropdown-menu');
if (!forumStaffDropdown) {
return;
}
const forums = getStoredForumsStaff();
console.log(`[Forums Staff] Mise à jour du menu avec ${forums.length} forums`);
forumStaffDropdown.innerHTML = '';
if (forums.length > 0) {
forums.forEach(forum => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = forum.url;
a.textContent = forum.name;
li.appendChild(a);
forumStaffDropdown.appendChild(li);
});
} else {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = 'forum/staff';
a.textContent = 'Voir tous les forums';
li.appendChild(a);
forumStaffDropdown.appendChild(li);
}
};
InitQueue.register('ForumsStaff:Extract', extractAndStoreForumsStaff, 5);
})();
/**
* MODULE: Forum Cards Mobile
* Transforme le tableau des forums en cards tactiles sur mobile
*/
(function initForumCardsMobile() {
'use strict';
// Ne s'exécuter que sur les pages du forum
if (!window.location.pathname.startsWith('/forum/')) {
return;
}
function transformTableToCards() {
const forumTable = document.querySelector('table.table tbody');
if (!forumTable) {
console.warn('[Forum Cards] Tableau forums introuvable');
return;
}
const rows = Array.from(forumTable.querySelectorAll('tr'));
if (rows.length === 0) {
console.warn('[Forum Cards] Aucun forum trouvé');
return;
}
const cardsContainer = document.createElement('div');
cardsContainer.className = 'forums-cards-container';
cardsContainer.setAttribute('role', 'list');
rows.forEach((row, index) => {
try {
const card = createForumCard(row, index);
if (card) {
cardsContainer.appendChild(card);
}
} catch (error) {
console.error('[Forum Cards] Erreur création carte:', error);
}
});
// Remplacement du tableau par les cards
const tableElement = forumTable.closest('table');
if (tableElement && tableElement.parentNode) {
tableElement.parentNode.insertBefore(cardsContainer, tableElement);
tableElement.style.display = 'none';
tableElement.setAttribute('data-mobile-hidden', 'true');
}
console.log(`[Forum Cards] ${rows.length} forums transformés en cards`);
}
function createForumCard(row, index) {
const cells = row.querySelectorAll('td');
if (cells.length < 3) {return null;}
// === EXTRACTION DES DONNÉES ===
// Cellule 1: Titre, description, modérateurs
const titleCell = cells[0];
const titleLink = titleCell.querySelector('p:first-child a');
if (!titleLink) {return null;}
const title = titleLink.textContent.trim();
const forumUrl = titleLink.getAttribute('href');
// Description (2e paragraphe)
const descriptionP = titleCell.querySelector('p:nth-child(2)');
const description = descriptionP ? descriptionP.textContent.trim() : '';
// Modérateurs (div avec classe contenant "mod" ou texte "Modérateurs")
const moderators = [];
const modElements = titleCell.querySelectorAll('div, span');
modElements.forEach(el => {
const text = el.textContent;
if (text.includes('Modérateur')) {
const links = el.querySelectorAll('a');
links.forEach(link => {
const name = link.textContent.trim();
if (name && !name.includes('[mod]')) {
moderators.push({
name: name.replace(/\[.*?\]/g, '').trim(),
url: link.getAttribute('href')
});
}
});
}
});
// Cellule 2: Nombre de sujets
const topicsText = cells[1]?.textContent.trim().replace('·', '').trim() || '0 sujets';
// Cellule 3: Nombre de messages
const messagesText = cells[2]?.textContent.trim() || '0 messages';
// Cellule 4: Dernière activité
let lastActivity = '';
let lastUser = '';
let lastTime = '';
if (cells[3]) {
const activityText = cells[3].textContent.trim();
const userLink = cells[3].querySelector('a');
if (userLink) {
lastUser = userLink.textContent.trim();
// Extraire le timestamp (format "Aujourd'hui (HH:MM)")
const timeMatch = activityText.match(/(\w+.*?\(\d{2}:\d{2}\))/);
lastTime = timeMatch ? timeMatch[1] : '';
}
lastActivity = activityText.replace('→', '').trim();
}
// === CRÉATION DE LA CARTE ===
const card = document.createElement('article');
card.className = 'forum-card';
card.setAttribute('role', 'listitem');
card.setAttribute('data-forum-index', index);
// Lien englobant (accessibility)
const cardLink = document.createElement('a');
cardLink.href = forumUrl;
cardLink.className = 'forum-card-link';
cardLink.setAttribute('aria-label', `Accéder au forum ${title}`);
// Contenu de la carte
let cardHTML = `
`;
if (description) {
cardHTML += `${description}
`;
}
if (moderators.length > 0) {
const modText = moderators.length > 2
? `${moderators[0].name}, ${moderators[1].name}...`
: moderators.map(m => m.name).join(', ');
cardHTML += `Mod: ${modText}
`;
}
cardHTML += ''; // Fermeture forum-footer
cardLink.innerHTML = cardHTML;
// FORCER les styles inline avec !important pour contourner Bootstrap
cardLink.style.setProperty('display', 'flex', 'important');
cardLink.style.setProperty('flex-direction', 'column', 'important');
cardLink.style.setProperty('align-items', 'flex-start', 'important');
cardLink.style.setProperty('justify-content', 'flex-start', 'important');
cardLink.style.setProperty('width', '100%', 'important');
card.appendChild(cardLink);
return card;
}
// === ENREGISTREMENT DANS InitQueue ===
function init() {
// Vérifier le mode mobile - doit être fait ici pour éviter d'accéder à document.body avant DOMContentLoaded
if (!document.body.classList.contains('mobile-mode')) {
console.log('[Forum Cards] Mode desktop détecté, transformation annulée');
return;
}
// Attendre que le DOM soit prêt
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', transformTableToCards);
} else {
transformTableToCards();
}
}
// Priorité 15: après navigation menus (10) mais avant autres transformations
InitQueue.register('ForumCards:MobileTransform', init, 15);
})();
/**
* MODULE: Mini-Chat FAB (Floating Action Button)
* Transforme le mini-chat latéral en overlay fullscreen sur mobile
*/
(function initMiniChatFAB() {
'use strict';
function createChatFAB() {
if (!document.body.classList.contains('mobile-mode')) {return;}
const miniChat = document.getElementById('flap');
if (!miniChat) {return;}
// Masquer le mini-chat par défaut sur mobile
miniChat.style.display = 'none';
miniChat.classList.add('mini-chat-overlay');
// Créer le bouton flottant
const fab = document.createElement('button');
fab.className = 'mini-chat-fab';
fab.innerHTML = 'MC ';
fab.setAttribute('aria-label', 'Ouvrir le mini-chat');
fab.setAttribute('type', 'button');
// Toggle du mini-chat
fab.addEventListener('click', (e) => {
e.preventDefault();
const isOpen = miniChat.style.display === 'block';
if (isOpen) {
miniChat.style.display = 'none';
fab.classList.remove('active');
document.body.style.overflow = '';
} else {
miniChat.style.display = 'block';
fab.classList.add('active');
document.body.style.overflow = 'hidden';
}
});
// Bouton fermeture dans le chat
const closeBtn = miniChat.querySelector('.close-btn');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
miniChat.style.display = 'none';
fab.classList.remove('active');
document.body.style.overflow = '';
});
}
document.body.appendChild(fab);
console.log('[Mini-Chat FAB] Initialisé');
}
InitQueue.register('MiniChat:FAB', createChatFAB, 20);
})();
// ============================================================================
// FORUM TOPICS - Afficher les titres complets
// Remplace les titres abrégés (...) par le texte complet du span invisible
// ============================================================================
(function () {
function useFullTopicTitles() {
// Cibler tous les sujets avec span.invisible
const topics = document.querySelectorAll('p.nomargin');
topics.forEach(topic => {
const invisibleSpan = topic.querySelector('span.invisible');
const link = topic.querySelector('a');
if (invisibleSpan && link) {
const fullTitle = invisibleSpan.textContent.trim();
const currentTitle = link.textContent.trim();
// Remplacer seulement si le titre est abrégé (contient ...)
if (currentTitle.includes('(...)') && fullTitle) {
link.textContent = fullTitle;
}
}
});
}
InitQueue.register('ForumTopics:FullTitles', useFullTopicTitles, 5);
})();
// ============================================================================
// MOBILE FORUM TOPICS - Stats + Smart Navigation
// Layout identique aux "Sujets permanents" (simple et épuré)
// Ajoute les stats (Msg · Vus) en texte simple sous le titre
// Adapte le lien du titre selon l'état (non lu → premier non lu, lu → dernier message)
// Rend toute la card cliquable (comme les Sujets permanents)
// ============================================================================
(function () {
function enrichForumTopicsCards() {
// N'exécuter qu'en mode mobile
if (!document.body.classList.contains('mobile-mode')) {return;}
// Cibler uniquement les DataTables de forum (pas les "Sujets permanents")
const forumTable = document.querySelector('table.dataTable');
if (!forumTable) {return;}
const rows = forumTable.querySelectorAll('tbody tr');
if (rows.length === 0) {return;}
rows.forEach(row => {
// Éviter le double traitement
if (row.hasAttribute('data-stats-added')) {return;}
row.setAttribute('data-stats-added', 'true');
// Récupérer les cellules
const titleCell = row.querySelector('td:nth-child(1)');
const msgCell = row.querySelector('td:nth-child(2)');
const viewsCell = row.querySelector('td:nth-child(3)');
if (!titleCell || !msgCell || !viewsCell) {return;}
// === SMART NAVIGATION ===
// Détecter si l'icône "premier message non lu" existe
const unreadIconLink = titleCell.querySelector('ul:first-of-type li a');
const titleLink = titleCell.querySelector('p > a');
if (unreadIconLink && titleLink) {
// Sujet NON LU : rediriger le titre vers le premier message non lu
titleLink.href = unreadIconLink.href;
titleLink.setAttribute('data-smart-redirect', 'first-unread');
}
// Si pas d'icône (sujet lu), le titre garde son URL d'origine (page 1)
// === CARD CLIQUABLE (comme Sujets permanents) ===
if (titleLink) {
row.style.cursor = 'pointer';
row.addEventListener('click', (e) => {
// Ne pas intercepter les clics sur les liens de tags
if (e.target.tagName === 'A' || e.target.closest('a')) {
return;
}
// Simuler le clic sur le titre
titleLink.click();
});
}
// === STATS SIMPLES (style "Sujets permanents") ===
// Extraire les valeurs
const msgCount = msgCell.textContent.trim();
const viewsCount = viewsCell.textContent.trim();
// Créer les stats avec icônes pour clarté
const statsContainer = document.createElement('div');
statsContainer.className = 'forum-topic-stats-mobile';
statsContainer.innerHTML = `
${msgCount}
·
${viewsCount}
`;
statsContainer.style.cssText = `
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--kr-text-secondary);
margin-bottom: 8px;
`;
// Insérer après le titre
const titleParagraph = titleCell.querySelector('p');
if (titleParagraph) {
titleParagraph.after(statsContainer);
}
});
console.log(`[Forum Topics Mobile] ${rows.length} sujets enrichis`);
}
InitQueue.register('ForumTopics:MobileStats', enrichForumTopicsCards, 25);
})();
// ============================================================================
// MOBILE FORUM PERMANENT TOPICS - Stats avec icônes
// Ajoute les icônes aux stats des "Sujets permanents"
// ============================================================================
(function () {
function enrichPermanentTopicsStats() {
// N'exécuter qu'en mode mobile
if (!document.body.classList.contains('mobile-mode')) {return;}
// Cibler les panel-default (Sujets permanents)
const panels = document.querySelectorAll('.panel-default');
if (panels.length === 0) {return;}
panels.forEach(panel => {
const rows = panel.querySelectorAll('.table tbody tr');
rows.forEach(row => {
// Éviter le double traitement
if (row.hasAttribute('data-permanent-icons-added')) {return;}
row.setAttribute('data-permanent-icons-added', 'true');
// Chercher les cellules de stats
const cells = row.querySelectorAll('td');
if (cells.length < 2) {return;}
// Les stats sont dans les cellules 2 et 3 (index 1 et 2)
const msgCell = cells[1];
const viewsCell = cells[2];
if (!msgCell || !viewsCell) {return;}
// Extraire les valeurs
const msgCount = msgCell.textContent.trim();
const viewsCount = viewsCell.textContent.trim();
// Créer un wrapper pour les stats avec icônes
const statsWrapper = document.createElement('div');
statsWrapper.className = 'permanent-topic-stats';
statsWrapper.innerHTML = `
${msgCount}
·
${viewsCount}
`;
statsWrapper.style.cssText = `
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: var(--kr-text-secondary);
order: 2;
margin: 0 0 2px 0;
`;
// Remplacer les cellules originales par le wrapper
msgCell.style.display = 'none';
viewsCell.style.display = 'none';
// Insérer le wrapper après la première cellule
const firstCell = cells[0];
if (firstCell) {
firstCell.after(statsWrapper);
}
});
});
console.log(`[Permanent Topics Mobile] Icônes ajoutées`);
}
InitQueue.register('PermanentTopics:Icons', enrichPermanentTopicsStats, 25);
})();
// ============================================================================
// MODULE : ForumHeader:MobileBreadcrumb
// Transforme le header forum en fil d'ariane + FAB button
// ============================================================================
(function() {
'use strict';
function transformForumHeader() {
// Uniquement en mode mobile
if (!document.body.classList.contains('mobile-mode')) {
return;
}
// Cibler le h1 qui contient "Taverne" et les liens
const forumHeading = document.querySelector('.container h1');
if (!forumHeading) {
console.warn('[Forum Header Mobile] h1 non trouvé');
return;
}
// Extraire le titre du forum (texte direct du h1)
const titleText = Array.from(forumHeading.childNodes)
.find(node => node.nodeType === Node.TEXT_NODE)
?.textContent.trim();
if (!titleText) {
return;
}
// Trouver les liens "Jeu (RP)" et "nouveau sujet"
const links = forumHeading.querySelectorAll('a');
if (links.length < 2) {
return;
}
const backLink = links[0]; // Lien "Jeu (RP)"
const newTopicLink = links[1]; // Lien "nouveau sujet"
// ========================================
// 1. CRÉER LE FIL D'ARIANE AVEC BOUTON (+)
// ========================================
const breadcrumbWrapper = document.createElement('div');
breadcrumbWrapper.className = 'forum-mobile-breadcrumb';
breadcrumbWrapper.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
font-size: 13px;
color: var(--kr-text-secondary);
`;
// Partie gauche : breadcrumb
const breadcrumbLeft = document.createElement('div');
breadcrumbLeft.style.cssText = `
display: flex;
align-items: center;
gap: 6px;
`;
// Cloner le lien "Jeu (RP)"
const breadcrumbLink = backLink.cloneNode(true);
breadcrumbLink.style.cssText = `
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--kr-text-secondary);
text-decoration: none;
font-weight: 400;
background: none;
padding: 0;
border: none;
border-radius: 0;
`;
// Ajouter le séparateur ">"
const separator = document.createElement('span');
separator.textContent = '›';
separator.style.cssText = `
color: var(--kr-text-secondary);
font-size: 13px;
margin: 0 2px;
`;
// Titre actuel (Taverne)
const currentTitle = document.createElement('span');
currentTitle.textContent = titleText;
currentTitle.style.cssText = `
color: var(--kr-text-secondary);
font-weight: 400;
`;
breadcrumbLeft.appendChild(breadcrumbLink);
breadcrumbLeft.appendChild(separator);
breadcrumbLeft.appendChild(currentTitle);
// Partie droite : bouton (+)
const fab = document.createElement('a');
fab.href = newTopicLink.href;
fab.className = 'forum-new-topic-fab';
fab.setAttribute('aria-label', 'Nouveau sujet');
fab.innerHTML = '+ ';
fab.style.cssText = `
width: 44px;
height: 44px;
min-width: 44px;
border-radius: 50%;
background: var(--kr-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
text-decoration: none;
transition: all 0.2s ease;
`;
// Feedback tactile
fab.addEventListener('touchstart', function() {
this.style.transform = 'scale(0.92)';
this.style.boxShadow = '0 1px 4px rgba(0, 0, 0, 0.3)';
});
fab.addEventListener('touchend', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.2)';
});
breadcrumbWrapper.appendChild(breadcrumbLeft);
breadcrumbWrapper.appendChild(fab);
// ========================================
// 2. REMPLACER LE CONTENU DU H1
// ========================================
forumHeading.innerHTML = '';
forumHeading.style.cssText = `
margin: 12px 16px !important;
padding: 0 !important;
background: transparent !important;
`;
forumHeading.appendChild(breadcrumbWrapper);
console.log('[Forum Header Mobile] Fil d\'ariane + FAB créés');
}
InitQueue.register('ForumHeader:MobileBreadcrumb', transformForumHeader, 25);
})();
// ============================================================================
// MODULE : ForumThread:MobileBreadcrumb
// Transforme le header des threads de forum en fil d'ariane + FAB button
// ============================================================================
(function() {
'use strict';
function transformForumThreadHeader() {
// Uniquement en mode mobile
if (!document.body.classList.contains('mobile-mode')) {
return;
}
// Vérifier qu'on est sur une page de thread (pas la liste des topics)
if (!window.location.pathname.includes('/forum/sujet/')) {
return;
}
// Cibler le h1 et tous les div.forum-top qui contiennent les boutons
const forumHeading = document.querySelector('.container h1.page-header');
const forumTops = document.querySelectorAll('.forum-top');
if (!forumHeading || forumTops.length === 0) {
console.warn('[Forum Thread Mobile] h1 ou .forum-top non trouvé');
return;
}
// Extraire le titre du thread (ignorer les nœuds texte vides)
const threadTitle = Array.from(forumHeading.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim())
.map(node => node.textContent.trim())
[0];
if (!threadTitle) {
console.warn('[Forum Thread Mobile] Titre du thread non trouvé');
return;
}
// Trouver les liens dans le premier .forum-top
const firstForumTop = forumTops[0];
const taverneLink = firstForumTop.querySelector('a[href*="forum/rp/"], a[href*="forum/hrp/"]'); // Lien vers le forum parent
const newTopicLink = firstForumTop.querySelector('a[href*="nouveau-sujet"]');
if (!taverneLink || !newTopicLink) {
console.warn('[Forum Thread Mobile] Liens non trouvés', { taverneLink: !!taverneLink, newTopicLink: !!newTopicLink });
return;
}
// Extraire le nom du forum parent (Taverne, etc.)
const forumName = taverneLink.textContent.trim();
// Déterminer la catégorie parente (RP/HRP) depuis l'URL
const forumUrl = taverneLink.href;
let categoryName = 'Jeu (RP)';
let categoryUrl = 'forum/rp';
if (forumUrl.includes('/forum/hrp/')) {
categoryName = 'Jeu (HRP)';
categoryUrl = 'forum/hrp';
}
// ========================================
// FONCTION HELPER : Créer un fil d'ariane
// ========================================
function createBreadcrumb(isBottom = false) {
const breadcrumbWrapper = document.createElement('div');
breadcrumbWrapper.className = isBottom ? 'forum-thread-mobile-breadcrumb-bottom' : 'forum-thread-mobile-breadcrumb';
breadcrumbWrapper.style.cssText = `
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
${isBottom ? 'margin-top: 16px; margin-bottom: 16px; padding: 12px 16px;' : 'margin-bottom: 16px;'}
font-size: 13px;
color: var(--kr-text-secondary);
`;
// Partie gauche : breadcrumb
const breadcrumbLeft = document.createElement('div');
breadcrumbLeft.style.cssText = `
display: flex;
align-items: center;
gap: 6px;
flex: 1;
min-width: 0;
`;
// Lien catégorie (Jeu RP)
const categoryLink = document.createElement('a');
categoryLink.href = categoryUrl;
categoryLink.textContent = categoryName;
categoryLink.style.cssText = `
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--kr-text-secondary);
text-decoration: none;
font-weight: 400;
white-space: nowrap;
`;
// Séparateur 1
const separator1 = document.createElement('span');
separator1.textContent = '›';
separator1.style.cssText = `
color: var(--kr-text-secondary);
font-size: 13px;
margin: 0 2px;
flex-shrink: 0;
`;
// Lien forum (Taverne)
const forumLink = document.createElement('a');
forumLink.href = taverneLink.href;
forumLink.textContent = forumName;
forumLink.style.cssText = `
color: var(--kr-text-secondary);
text-decoration: none;
font-weight: 400;
white-space: nowrap;
`;
// Séparateur 2
const separator2 = document.createElement('span');
separator2.textContent = '›';
separator2.style.cssText = `
color: var(--kr-text-secondary);
font-size: 13px;
margin: 0 2px;
flex-shrink: 0;
`;
// Titre du thread (tronqué si nécessaire)
const threadTitleSpan = document.createElement('span');
threadTitleSpan.textContent = threadTitle;
threadTitleSpan.style.cssText = `
color: var(--kr-text-secondary);
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
`;
breadcrumbLeft.appendChild(categoryLink);
breadcrumbLeft.appendChild(separator1);
breadcrumbLeft.appendChild(forumLink);
breadcrumbLeft.appendChild(separator2);
breadcrumbLeft.appendChild(threadTitleSpan);
// Partie droite : bouton (+)
const fab = document.createElement('a');
fab.href = newTopicLink.href;
fab.className = 'forum-new-topic-fab';
fab.setAttribute('aria-label', 'Nouveau sujet');
fab.innerHTML = '+ ';
fab.style.cssText = `
width: 44px;
height: 44px;
min-width: 44px;
flex-shrink: 0;
border-radius: 50%;
background: var(--kr-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
text-decoration: none;
transition: all 0.2s ease;
`;
// Feedback tactile
fab.addEventListener('touchstart', function() {
this.style.transform = 'scale(0.92)';
this.style.boxShadow = '0 1px 4px rgba(0, 0, 0, 0.3)';
});
fab.addEventListener('touchend', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.2)';
});
breadcrumbWrapper.appendChild(breadcrumbLeft);
breadcrumbWrapper.appendChild(fab);
return breadcrumbWrapper;
}
// ========================================
// 1. REMPLACER LE CONTENU DU H1
// ========================================
forumHeading.innerHTML = '';
forumHeading.style.cssText = `
margin: 12px 16px !important;
padding: 0 !important;
background: transparent !important;
`;
forumHeading.appendChild(createBreadcrumb(false));
// ========================================
// 2. TRANSFORMER LES .forum-top (sauf le premier si c'est le même que le h1)
// ========================================
forumTops.forEach((forumTop, index) => {
// Sauter le premier si on a déjà modifié le h1
if (index === 0) {
return;
}
forumTop.innerHTML = '';
forumTop.style.cssText = `
margin: 0 !important;
padding: 0 !important;
background: transparent !important;
border: none !important;
`;
forumTop.appendChild(createBreadcrumb(true));
});
// ========================================
// 3. NETTOYER LES ÉLÉMENTS SUPERFLUS
// ========================================
// Masquer les vieux boutons Bootstrap du milieu (actions redondantes)
const actionButtons = document.querySelector('.container h1.page-header + .row + .row .btn-group');
if (actionButtons) {
actionButtons.style.display = 'none';
}
// Masquer les liens d'actions redondants (nouveau sujet, répondre, etc.)
const forumTopActions = document.querySelectorAll('.forum-top a[href*="nouveau-sujet"], .forum-top a[href*="repondre"]');
forumTopActions.forEach(action => {
action.parentElement.style.display = 'none';
});
console.log('[Forum Thread Mobile] Fil d\'ariane + FAB créés (haut et bas) - Éléments superflus masqués');
}
InitQueue.register('ForumThread:MobileBreadcrumb', transformForumThreadHeader, 25);
})();
// ============================================================================
// MODULE : ForumThread:HideTags
// Masque les tags dans la vue détail du fil (ils sont inutiles et gênent la mise en page)
// ============================================================================
(function() {
'use strict';
function hideForumThreadTags() {
console.log('[Forum Thread:HideTags] Function called');
// Vérifier qu'on est sur une page de thread détail
const pathname = window.location.pathname;
if (!pathname.includes('/forum/sujet/')) {
console.log('[Forum Thread:HideTags] Not on a forum thread page');
return;
}
console.log('[Forum Thread:HideTags] On forum thread page, hiding tags after delay');
// Les tags sont ajoutés dynamiquement, donc on doit attendre un peu
// Utiliser un délai pour s'assurer qu'ils sont présents
setTimeout(() => {
console.log('[Forum Thread:HideTags] setTimeout fired');
// Chercher tous les liens de tags (sans slash initial dans le href)
const tagLinks = Array.from(document.querySelectorAll('a[href*="forum/tags"]'));
console.log('[Forum Thread:HideTags] Found', tagLinks.length, 'tag links');
if (tagLinks.length === 0) {
console.log('[Forum Thread:HideTags] No tag links found');
return;
}
// Chercher le conteneur parent qui contient TOUS les tags
let current = tagLinks[0];
while (current && current !== document.body) {
current = current.parentElement;
if (current && current.querySelectorAll('a[href*="forum/tags"]').length === tagLinks.length) {
// Vérifier que ce n'est pas trop large
const allLinks = current.querySelectorAll('a');
console.log('[Forum Thread:HideTags] Found container with', allLinks.length, 'total links');
if (allLinks.length < 50) { // Éviter de masquer la page entière
current.style.display = 'none';
console.log('[Forum Thread] Tags masqués (' + tagLinks.length + ' tag(s))');
return;
}
}
}
// Fallback: masquer les éléments parents directs des tags
tagLinks.forEach(link => {
const parent = link.parentElement;
if (parent && !parent.className.includes('forum')) {
parent.style.display = 'none';
}
});
console.log('[Forum Thread] Tags masqués (fallback)');
}, 500); // 500ms devrait suffire
}
InitQueue.register('ForumThread:HideTags', hideForumThreadTags, 26);
})();
// ============================================================================
// MODULE : ForumPosts:Restructure
// Restructure complètement le DOM des posts pour une mise en page Bootstrap propre
// Row 1: col-xs-4 (user-info) + col-xs-8 (boutons)
// Row 2: col-xs-12 (contenu message)
// ============================================================================
(function() {
'use strict';
const MOBILE_BREAKPOINT = 768;
function restructureForumPosts() {
// Uniquement en mode mobile (basé sur la largeur de l'écran)
if (window.innerWidth >= MOBILE_BREAKPOINT) {
return;
}
// Vérifier qu'on est sur une page de thread
if (!window.location.pathname.includes('/forum/sujet/')) {
return;
}
// Sélectionner tous les posts non-restructurés
const posts = document.querySelectorAll('ul.media-list.forum > li.media:not([data-restructured])');
if (posts.length === 0) {
return;
}
posts.forEach(post => {
// 1. Récupérer les éléments existants
const userInfo = post.querySelector('.pull-left, .user-info');
const pullRight = post.querySelector('.pull-right');
const mediaBody = post.querySelector('.media-body');
if (!userInfo || !mediaBody) {
return;
}
// Sauvegarder TOUT le contenu de media-body avant modification
const originalMediaBodyHTML = mediaBody.innerHTML;
// 2. Créer Row 1 (header: user + boutons)
const headerRow = document.createElement('div');
headerRow.className = 'row forum-header';
// Colonne 1: User info
const userCol = document.createElement('div');
userCol.className = 'col-xs-8 forum-user-section';
// Cloner le userInfo pour le déplacer
const userInfoClone = userInfo.cloneNode(true);
userCol.appendChild(userInfoClone);
// Extraire et ajouter la date depuis le bouton de date
const dateButton = mediaBody.querySelector('.btn-group.btn-group-xs:first-child, div.btn-group.btn-group-xs:first-child');
if (dateButton) {
const dateText = dateButton.textContent.replace('posté', '').replace('modifié', '').trim();
const dateElement = document.createElement('div');
dateElement.className = 'post-date-mobile';
dateElement.textContent = dateText;
userCol.appendChild(dateElement);
}
// Colonne 2: Boutons d'action
const actionsCol = document.createElement('div');
actionsCol.className = 'col-xs-4 forum-actions-section';
if (pullRight) {
const pullRightClone = pullRight.cloneNode(true);
actionsCol.appendChild(pullRightClone);
}
headerRow.appendChild(userCol);
headerRow.appendChild(actionsCol);
// 3. Créer Row 2 (contenu du message)
const contentRow = document.createElement('div');
contentRow.className = 'row forum-content-row';
const contentCol = document.createElement('div');
contentCol.className = 'col-xs-12 forum-content-section';
// Créer un nouveau mediaBody temporaire pour extraire le contenu
const tempDiv = document.createElement('div');
tempDiv.innerHTML = originalMediaBodyHTML;
// Retirer userInfo, pullRight, dateButton du tempDiv
const tempUserInfo = tempDiv.querySelector('.pull-left, .user-info');
const tempPullRight = tempDiv.querySelector('.pull-right');
const tempDateButton = tempDiv.querySelector('.btn-group.btn-group-xs:first-child');
if (tempUserInfo) tempUserInfo.remove();
if (tempPullRight) tempPullRight.remove();
if (tempDateButton) tempDateButton.remove();
// Tout ce qui reste est le contenu du message
contentCol.innerHTML = tempDiv.innerHTML;
contentRow.appendChild(contentCol);
// 4. Vider le post et reconstruire
// Supprimer le .pull-left original (avatar dupliqué)
if (userInfo && userInfo.parentElement === post) {
userInfo.remove();
}
// Vider le mediaBody complètement
mediaBody.innerHTML = '';
// Insérer les nouvelles rows
mediaBody.appendChild(headerRow);
mediaBody.appendChild(contentRow);
// 5. Marquer comme restructuré
post.setAttribute('data-restructured', 'true');
post.classList.add('forum-post-restructured');
});
console.log(`[Forum Restructure] ${posts.length} posts restructurés avec nouveau layout`);
}
InitQueue.register('ForumPosts:Restructure', restructureForumPosts, 26);
})();
function modifyNavigationMenus() {
// Modification des liens des boutons principaux du menu
const menuLinks = {
'Jouer': 'jouer/plateau',
'Règles': 'regles/avancees',
'Monde': 'monde/evenements',
'Communauté': 'communaute/membres'
};
let forumOriginalItem = null;
// Vérifier si on est en mode mobile
const isMobileMode = document.body.classList.contains('mobile-mode');
// Modification des liens existants et ajout du comportement de redirection
document.querySelectorAll('.navbar-nav .dropdown > a.dropdown-toggle').forEach(link => {
const text = link.textContent.trim().replace(/\s*\n.*$/, '');
// Traiter le menu Forum séparément (le menu original avant transformation)
if (text.startsWith('Forum') && !text.includes('HRP') && !text.includes('RP')) {
forumOriginalItem = link.closest('li.dropdown');
return;
}
const menuKey = Object.keys(menuLinks).find(key => text.includes(key));
if (menuKey && menuLinks[menuKey]) {
link.href = menuLinks[menuKey];
// EN DESKTOP UNIQUEMENT : Supprimer data-toggle pour empêcher le dropdown et forcer la navigation
// EN MOBILE : Garder data-toggle pour permettre l'affichage du sous-menu
if (!isMobileMode) {
link.removeAttribute('data-toggle');
// Marquer comme modifié pour éviter de ré-ajouter l'événement
if (!link.hasAttribute('data-nav-modified')) {
link.setAttribute('data-nav-modified', 'true');
// S'assurer que le clic navigue vers la nouvelle URL
link.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = this.href;
return false;
});
}
} else {
// EN MOBILE : Conserver/restaurer data-toggle="dropdown" pour Bootstrap 3
link.setAttribute('data-toggle', 'dropdown');
}
}
});
// Remplacer le menu Forum original par Forum RP et Forum HRP
if (forumOriginalItem && !document.querySelector('[data-forums-added]')) {
// Récupérer le dropdown menu original pour le cloner
const originalDropdown = forumOriginalItem.querySelector('.dropdown-menu');
// Créer Forum RP
const forumRpLi = document.createElement('li');
forumRpLi.className = 'dropdown';
forumRpLi.setAttribute('data-forums-added', 'rp');
forumRpLi.innerHTML = `
Forum RP
`;
// Créer Forum HRP en clonant le dropdown original
const forumHrpLi = document.createElement('li');
forumHrpLi.className = 'dropdown';
forumHrpLi.setAttribute('data-forums-added', 'hrp');
forumHrpLi.innerHTML = `
Forum HRP
`;
// Cloner le dropdown original et supprimer uniquement le lien "Jeu (RP)"
if (originalDropdown) {
const clonedDropdown = originalDropdown.cloneNode(true);
// Supprimer le lien "Jeu (RP)" du dropdown cloné
const rpLink = Array.from(clonedDropdown.querySelectorAll('li > a'))
.find(a => a.textContent.includes('Jeu (RP)'));
if (rpLink && rpLink.parentElement) {
rpLink.parentElement.remove();
}
forumHrpLi.appendChild(clonedDropdown);
}
// Insérer Forum RP avant le Forum original
if (forumOriginalItem.parentElement) {
forumOriginalItem.parentElement.insertBefore(forumRpLi, forumOriginalItem);
// Insérer Forum HRP après Forum RP (donc avant l'original aussi)
forumOriginalItem.parentElement.insertBefore(forumHrpLi, forumOriginalItem);
}
// Supprimer le menu Forum original
forumOriginalItem.remove();
// EN DESKTOP UNIQUEMENT : Ajouter les comportements de navigation directe
if (!isMobileMode) {
const forumRpLink = forumRpLi.querySelector('a.dropdown-toggle');
if (forumRpLink) {
forumRpLink.removeAttribute('data-toggle');
forumRpLink.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = this.href;
return false;
});
}
const forumHrpLink = forumHrpLi.querySelector('a.dropdown-toggle');
if (forumHrpLink) {
forumHrpLink.removeAttribute('data-toggle');
forumHrpLink.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = this.href;
return false;
});
}
} else {
// EN MOBILE : Garder data-toggle pour les dropdowns
const forumRpLink = forumRpLi.querySelector('a.dropdown-toggle');
if (forumRpLink) {
forumRpLink.setAttribute('data-toggle', 'dropdown');
}
const forumHrpLink = forumHrpLi.querySelector('a.dropdown-toggle');
if (forumHrpLink) {
forumHrpLink.setAttribute('data-toggle', 'dropdown');
}
}
// Créer les trois nouveaux menus : Forum Communauté, Forum Débats, Forum Staff
const forumMenuParent = forumRpLi.parentElement;
if (forumMenuParent && !document.querySelector('[data-forums-added="forum-communaute"]')) {
// Vérifier si Staff existe dans le dropdown-menu original
let staffExists = false;
if (originalDropdown) {
const staffLink = Array.from(originalDropdown.querySelectorAll('li > a'))
.find(a => a.textContent.toLowerCase().includes('staff'));
staffExists = !!staffLink;
}
// Menu Forum Communauté
const forumCommunauteLi = document.createElement('li');
forumCommunauteLi.className = 'dropdown';
forumCommunauteLi.setAttribute('data-forums-added', 'forum-communaute');
forumCommunauteLi.innerHTML = `
Forum Communauté
`;
// Menu Forum Débats
const forumDebatsLi = document.createElement('li');
forumDebatsLi.className = 'dropdown';
forumDebatsLi.setAttribute('data-forums-added', 'forum-debats');
forumDebatsLi.innerHTML = `
Forum Débats
`;
// Menu Forum Staff (uniquement si Staff existe dans le menu original)
let forumStaffLi = null;
if (staffExists) {
forumStaffLi = document.createElement('li');
forumStaffLi.className = 'dropdown';
forumStaffLi.setAttribute('data-forums-added', 'forum-staff');
forumStaffLi.innerHTML = `
Forum Staff
`;
}
// Insérer les nouveaux menus après Forum HRP
forumMenuParent.insertBefore(forumCommunauteLi, forumHrpLi.nextSibling);
forumMenuParent.insertBefore(forumDebatsLi, forumCommunauteLi.nextSibling);
if (forumStaffLi) {
forumMenuParent.insertBefore(forumStaffLi, forumDebatsLi.nextSibling);
}
// EN DESKTOP UNIQUEMENT : Ajouter les comportements de navigation directe
if (!isMobileMode) {
const menusToUpdate = [forumCommunauteLi, forumDebatsLi];
if (forumStaffLi) menusToUpdate.push(forumStaffLi);
menusToUpdate.forEach(li => {
const link = li.querySelector('a.dropdown-toggle');
if (link) {
link.removeAttribute('data-toggle');
link.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = this.href;
return false;
});
}
});
} else {
// EN MOBILE : Garder data-toggle pour les dropdowns
const menusToUpdate = [forumCommunauteLi, forumDebatsLi];
if (forumStaffLi) menusToUpdate.push(forumStaffLi);
menusToUpdate.forEach(li => {
const link = li.querySelector('a.dropdown-toggle');
if (link) {
link.setAttribute('data-toggle', 'dropdown');
}
});
}
}
}
// Ajout du menu Statistiques (une seule fois)
const communauteItem = Array.from(document.querySelectorAll('.navbar-nav > li.dropdown'))
.find(li => li.querySelector('a.dropdown-toggle')?.textContent.includes('Communauté') && !li.getAttribute('data-forums-added'));
if (communauteItem && !document.querySelector('[data-stats-menu-added]')) {
const statsLi = document.createElement('li');
statsLi.className = 'dropdown';
statsLi.setAttribute('data-stats-menu-added', 'true');
statsLi.innerHTML = `
Statistiques
`;
if (communauteItem.parentElement) {
communauteItem.parentElement.insertBefore(statsLi, communauteItem.nextSibling);
}
// EN DESKTOP UNIQUEMENT : Ajouter le comportement de navigation directe
if (!isMobileMode) {
const statsLink = statsLi.querySelector('a.dropdown-toggle');
if (statsLink) {
statsLink.removeAttribute('data-toggle');
statsLink.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = this.href;
return false;
});
}
} else {
// EN MOBILE : Garder data-toggle pour le dropdown
const statsLink = statsLi.querySelector('a.dropdown-toggle');
if (statsLink) {
statsLink.setAttribute('data-toggle', 'dropdown');
}
}
}
}
function markActiveIcons() {
const markers = [
{ text: 'Membres actifs', cls: 'kr-icon-members' },
{ text: 'Personnages actifs', cls: 'kr-icon-characters' },
{ text: 'Personnes en ligne', cls: 'kr-icon-online' },
{ text: 'Présentation', cls: 'kr-icon-presentation' },
{ text: 'Médailles', cls: 'kr-icon-medals' }
];
markers.forEach(m =>
document.querySelectorAll('.' + m.cls).forEach(n => n.classList.remove(m.cls))
);
const all = Array.from(document.querySelectorAll('*'));
markers.forEach(m => {
let best = null;
for (const el of all) {
const txt = (el.textContent || '').trim();
if (!txt.includes(m.text)) {continue;}
const hasIcon = !!el.querySelector('i.fa, i.falarge, .glyphicon, svg');
const score = (hasIcon ? 100 : 0) + Math.max(0, 200 - Math.min(txt.length, 200));
if (!best || score > best.score) {best = { el, score };}
}
if (best?.el) {best.el.classList.add(m.cls);}
});
}
function replaceMcAnchors() {
Array.from(document.querySelectorAll('a'))
.filter(a => (a.textContent || '').trim() === 'MC')
.forEach(a => {
a.classList.add('kr-mc-icon');
const title = a.getAttribute('data-original-title') || a.getAttribute('title') ||
(a.classList.contains('open') ? 'ouvrir le mini-chat' : 'fermer le mini-chat');
if (title) {a.setAttribute('aria-label', title);}
a.removeAttribute('aria-hidden');
});
}
function replaceNavbarBrand() {
const brand = document.querySelector('.navbar-brand');
if (!brand) {return;}
const variant = getVariant();
const idx = CONFIG.LOGO_MAP[variant] || 1;
const url = `http://img7.kraland.org/2/world/logo${idx}.gif`;
const existing = brand.querySelector('img.kr-logo');
if (existing?.src?.includes(`logo${idx}.gif`)) {return;}
brand.innerHTML = '';
const img = document.createElement('img');
img.className = 'kr-logo';
img.src = url;
img.alt = 'Kraland';
img.style.height = '28px';
img.style.verticalAlign = 'middle';
brand.appendChild(img);
}
function replaceSImages() {
const map = { 's1.gif': '♂', 's2.gif': '♀', 's3.gif': '⚧' };
const imgs = Array.from(document.querySelectorAll('img'))
.filter(i => /img7\.kraland\.org\/.+\/(s[123]\.gif)$/.test(i.src));
imgs.forEach(img => {
const m = img.src.match(/(s[123]\.gif)$/);
const key = m?.[1];
const ch = map[key] || '';
const span = document.createElement('span');
span.className = 'kr-symbol kr-symbol-' + (key || 's');
span.setAttribute('aria-hidden', 'true');
span.textContent = ch;
if (img.alt) {
const sr = document.createElement('span');
sr.className = 'kr-sr-only';
sr.textContent = img.alt;
span.appendChild(sr);
}
const sexAncestor = img.closest('[id*="sex"]');
if (sexAncestor) {
const strong = document.createElement('strong');
strong.appendChild(span);
img.replaceWith(strong);
} else {
img.replaceWith(span);
}
});
}
function reorderBtnGroupXs() {
document.querySelectorAll('span.btn-group-xs').forEach(btn => {
const parent = btn.parentElement;
if (!parent) {return;}
const strong = parent.querySelector('strong');
if (strong && strong.parentElement === parent && btn.nextElementSibling !== strong) {
parent.insertBefore(btn, strong);
return;
}
const textNode = Array.from(parent.childNodes)
.find(n => n.nodeType === 3 && n.textContent?.trim().length > 0);
if (textNode && btn.nextSibling !== textNode) {
parent.insertBefore(btn, textNode);
}
});
}
function ensureSexStrong() {
document.querySelectorAll('[id*="ajax-sex"]').forEach(el => {
if (el.querySelector('strong')) {return;}
const sym = el.querySelector('.kr-symbol');
if (sym && sym.parentElement) {
const strong = document.createElement('strong');
sym.parentElement.replaceChild(strong, sym);
strong.appendChild(sym);
return;
}
const tn = Array.from(el.childNodes)
.find(n => n.nodeType === 3 && n.textContent?.trim().length > 0);
if (tn && tn.parentElement) {
const txt = tn.textContent.trim();
const strong = document.createElement('strong');
strong.textContent = txt;
tn.textContent = tn.textContent.replace(txt, '');
tn.parentElement.insertBefore(strong, tn.nextSibling);
}
});
}
function ensureFooterSticky() {
const footer = document.querySelector('footer, .footer, .contentinfo');
if (!footer) {return;}
// Déplacer le footer à la fin du body pour que le flexbox fonctionne correctement
if (footer.nextSibling !== null) {
document.body.appendChild(footer);
}
const selectors = ['a[href="#top"]', 'a.to-top', '.back-to-top', '.scroll-top', 'a.well.well-sm'];
let back = null;
for (const s of selectors) {
back = document.querySelector(s);
if (back) {break;}
}
if (back) {
back.classList.add('kraland-back-to-top');
if (!back.getAttribute('aria-label')) {back.setAttribute('aria-label', 'Remonter en haut');}
const whiteContainer = footer.querySelector('.container.white');
if (whiteContainer) {
whiteContainer.appendChild(back);
} else if (back.parentElement !== footer) {
footer.appendChild(back);
}
}
// Bootstrap 3.3 sticky footer: use margin-bottom on body
if (!document.body.style.marginBottom) {
document.body.style.marginBottom = '60px';
}
}
/**
* Affiche les informations de version du CSS dans le footer
* - Version actuelle du userscript chargé
* - Dernière version disponible sur GitHub (ou serveur local en dev)
*/
function displayVersionInfo() {
const footer = document.querySelector('footer, .footer, .contentinfo');
if (!footer) {return;}
// Créer l'élément de version s'il n'existe pas
let versionDiv = footer.querySelector('.kraland-css-version');
if (!versionDiv) {
versionDiv = document.createElement('div');
versionDiv.className = 'kraland-css-version';
versionDiv.style.cssText = 'text-align: center; padding: 10px; font-size: 12px; color: #666;';
const container = footer.querySelector('.container.white') || footer;
container.appendChild(versionDiv);
}
// Afficher la version actuelle
const currentVersion = CURRENT_VERSION !== '__USERSCRIPT_VERSION__' ? CURRENT_VERSION : 'dev';
// En mode dev, afficher simplement "dev" sans essayer de fetch
if (currentVersion === 'dev') {
versionDiv.innerHTML = `CSS : version courante ${currentVersion} ℹ️ (mode développement) `;
return;
}
versionDiv.innerHTML = `CSS : version courante ${currentVersion} , dernière version chargement... `;
// Déterminer l'URL du fichier version.json (GitHub en prod)
const versionUrl = 'https://raw.githubusercontent.com/arnaudroubinet/kraland-css/refs/heads/main/version.json';
// Récupérer la dernière version disponible
fetch(versionUrl)
.then(response => {
if (!response.ok) {throw new Error('Fetch failed');}
return response.json();
})
.then(data => {
const latestSpan = document.getElementById('latest-version');
if (latestSpan) {
latestSpan.innerHTML = `${data.version} `;
// Comparer les versions et afficher un indicateur si mise à jour disponible
if (data.version !== currentVersion) {
latestSpan.innerHTML += ' ⚠️ (mise à jour disponible) ';
}
}
})
.catch(error => {
console.error('[Version Info] Erreur lors de la récupération de la version:', error);
const latestSpan = document.getElementById('latest-version');
if (latestSpan) {
latestSpan.textContent = 'erreur';
}
});
}
function relocateKramailToLeft() {
const colT = document.getElementById('col-t');
const colLeft = document.getElementById('col-left');
if (!colT || !colLeft) {return;}
// Supprimer les blocs Kramail
colT.querySelectorAll('a[href*="kramail"]').forEach(a => {
const container = a.closest('div,section,li,article');
if (container && container !== colT && container.id !== 'col-t') {
container.remove();
} else {
a.remove();
}
});
// Sélectionner les éléments à déplacer
const selectors = ['.ds_users', '.ds_characters', '.ds_online'].map(c => `a${c}`);
let toMove = [];
selectors.forEach(sel => toMove.push(...colT.querySelectorAll(sel)));
if (toMove.length < 3) {
const texts = ['Membres actifs', 'Personnages actifs', 'Personnes en ligne'];
texts.forEach(txt => {
const el = Array.from(colT.querySelectorAll('a, li, div, p'))
.find(n => n.textContent?.includes(txt));
if (el && !toMove.includes(el)) {toMove.push(el);}
});
}
toMove = toMove.filter(el => el && !colLeft.contains(el));
if (!toMove.length) {return;}
let container = colLeft.querySelector('.kraland-metrics');
if (!container) {
container = document.createElement('div');
container.className = 'kraland-metrics list-group';
colLeft.appendChild(container);
}
toMove.forEach(el => container.appendChild(el));
}
function restructurePlatoColumns() {
if (!isPlatoPage()) {return;}
const colLeft = document.getElementById('col-left');
const colRight = document.getElementById('col-right');
if (!colLeft || !colRight) {return;}
const parent = colLeft.parentElement;
if (!parent || !parent.classList.contains('row')) {return;}
let colLeftest = document.getElementById('col-leftest');
if (!colLeftest) {
colLeftest = document.createElement('div');
colLeftest.id = 'col-leftest';
colLeftest.className = 'col-md-1';
parent.insertBefore(colLeftest, colLeft);
}
if (colRight.classList.contains('col-md-9')) {
colRight.classList.remove('col-md-9');
colRight.classList.add('col-md-8');
}
}
function moveBtnGroupToCols() {
if (!isPlatoPage()) {return;}
const btnGroupXs = document.querySelector('.btn-group-xs.center');
const colLeftest = document.getElementById('col-leftest');
if (!btnGroupXs || !colLeftest || colLeftest.contains(btnGroupXs)) {return;}
let wrapper = document.getElementById('col-leftest-stats');
if (!wrapper) {
wrapper = document.createElement('div');
wrapper.id = 'col-leftest-stats';
wrapper.className = 'panel panel-body';
colLeftest.appendChild(wrapper);
}
if (!wrapper.contains(btnGroupXs)) {
wrapper.appendChild(btnGroupXs);
}
}
function transformToBootstrapGrid() {
const colLeftestStats = document.getElementById('col-leftest-stats');
const skillsPanel = document.getElementById('skills-panel');
if (colLeftestStats && !colLeftestStats.classList.contains('grid-transformed')) {
const btnGroup = colLeftestStats.querySelector('.btn-group-xs');
if (btnGroup) {
const buttons = Array.from(btnGroup.querySelectorAll('a.btn'));
if (buttons.length > 0) {
colLeftestStats.innerHTML = '';
const row = document.createElement('div');
row.className = 'row';
buttons.forEach(btn => {
const col = document.createElement('div');
col.className = 'col-md-6';
col.style.display = 'flex';
col.style.justifyContent = 'center';
col.style.alignItems = 'center';
col.style.marginBottom = '8px';
col.appendChild(btn);
row.appendChild(col);
});
colLeftestStats.appendChild(row);
colLeftestStats.classList.add('grid-transformed');
}
}
}
if (skillsPanel && !skillsPanel.classList.contains('grid-transformed')) {
const items = Array.from(skillsPanel.querySelectorAll('a.list-group-item'));
if (items.length > 0) {
skillsPanel.innerHTML = '';
const row = document.createElement('div');
row.className = 'row';
items.forEach(item => {
const col = document.createElement('div');
col.className = 'col-md-6';
col.style.display = 'flex';
col.style.justifyContent = 'center';
col.style.alignItems = 'center';
col.style.marginBottom = '8px';
col.appendChild(item);
row.appendChild(col);
});
skillsPanel.appendChild(row);
skillsPanel.classList.add('grid-transformed');
}
}
}
function moveSkillsPanelToCols() {
const colLeft = document.getElementById('col-left');
const colLeftest = document.getElementById('col-leftest');
if (!colLeft || !colLeftest) {return;}
const skillsPanelOld = colLeft.querySelector('.panel.panel-default');
if (!skillsPanelOld) {return;}
const panelBody = skillsPanelOld.querySelector('.panel-body');
if (!panelBody || panelBody.id) {return;}
panelBody.id = 'skills-panel';
colLeftest.appendChild(panelBody);
skillsPanelOld.remove();
}
function nameLeftSidebarDivs() {
const colLeft = document.getElementById('col-left');
if (!colLeft) {return;}
const mainPanel = colLeft.querySelector('.panel.panel-body');
if (mainPanel && !mainPanel.id) {mainPanel.id = 'player-main-panel';}
const headerSection = colLeft.querySelector('.list-group');
if (headerSection && !headerSection.id) {headerSection.id = 'player-header-section';}
const vitalsSection = colLeft.querySelector('div.t.row');
if (vitalsSection && !vitalsSection.id) {vitalsSection.id = 'player-vitals-section';}
const allTDivs = Array.from(colLeft.querySelectorAll('div.t'));
if (allTDivs.length > 0) {
const actionsSection = allTDivs[allTDivs.length - 1];
if (!actionsSection.id && actionsSection.querySelector('a.btn-primary')) {
actionsSection.id = 'player-actions-section';
}
}
}
function transformSkillsToIcons() {
const skillsPanel = document.getElementById('skills-panel');
if (!skillsPanel || skillsPanel.dataset.iconsTransformed) {return;}
skillsPanel.querySelectorAll('.list-group-item').forEach(item => {
const heading = item.querySelector('.list-group-item-heading');
const skillName = heading?.querySelector('.mini')?.textContent || '';
const level = item.querySelector('.mention')?.textContent || '0';
const iconCode = CONFIG.SKILL_ICONS[skillName];
if (!iconCode) {return;}
const iconUrl = `http://img7.kraland.org/2/mat/94/${iconCode}.gif`;
const originalClasses = item.className;
item.className = originalClasses + ' btn btn-default mini';
item.innerHTML = '';
const iconContainer = createIconContainer(iconUrl, skillName, level);
Object.assign(item.style, {
display: 'flex', alignItems: 'center',
justifyContent: 'center', padding: '8px',
width: '40px', height: '40px',
minWidth: '40px', minHeight: '40px'
});
item.appendChild(iconContainer);
});
skillsPanel.dataset.iconsTransformed = '1';
}
function transformStatsToNotifications() {
const colLeftestStats = document.getElementById('col-leftest-stats');
if (!colLeftestStats || colLeftestStats.dataset.badgesTransformed) {return;}
colLeftestStats.querySelectorAll('.col-md-6 > a.btn').forEach(statBtn => {
const text = statBtn.textContent.trim();
const match = text.match(/^([A-Z]+)/);
const cleanStatName = match ? match[1] : text;
const levelMatch = text.match(/(\d+)$/);
const number = levelMatch ? levelMatch[1] : '0';
while (statBtn.firstChild) {statBtn.removeChild(statBtn.firstChild);}
const originalClasses = statBtn.className;
statBtn.className = originalClasses + ' list-group-item ds_game';
Object.assign(statBtn.style, {
padding: '8px', display: 'flex',
alignItems: 'center', justifyContent: 'center',
width: '40px', height: '40px',
minWidth: '40px', minHeight: '40px'
});
statBtn.title = cleanStatName;
const displayMode = getStatsDisplayMode();
// Créer le conteneur principal (toujours avec badge)
const container = document.createElement('div');
Object.assign(container.style, {
position: 'relative', display: 'inline-block',
width: '32px', height: '32px'
});
if (displayMode === 'text') {
// Mode texte : afficher uniquement l'abréviation (3 lettres)
const textSpan = document.createElement('span');
textSpan.textContent = cleanStatName.substring(0, 3).toUpperCase();
Object.assign(textSpan.style, {
fontWeight: 'bold',
fontSize: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '32px',
height: '32px'
});
container.appendChild(textSpan);
} else {
// Mode icône (par défaut)
const iconCode = CONFIG.STAT_ICONS[cleanStatName];
if (iconCode) {
const iconUrl = `http://img7.kraland.org/2/mat/94/${iconCode}.gif`;
const img = document.createElement('img');
img.src = iconUrl;
img.alt = cleanStatName;
img.title = cleanStatName;
Object.assign(img.style, {
display: 'block', width: '32px', height: '32px'
});
container.appendChild(img);
} else {
// Fallback SVG
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '32');
svg.setAttribute('height', '32');
svg.setAttribute('viewBox', '0 0 32 32');
svg.style.display = 'block';
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('width', '32');
rect.setAttribute('height', '32');
rect.setAttribute('fill', '#f0f0f0');
rect.setAttribute('stroke', '#ccc');
svg.appendChild(rect);
const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgText.setAttribute('x', '16');
svgText.setAttribute('y', '20');
svgText.setAttribute('text-anchor', 'middle');
svgText.setAttribute('font-size', '14');
svgText.setAttribute('font-weight', 'bold');
svgText.setAttribute('fill', '#333');
svgText.textContent = cleanStatName.substring(0, 2).toUpperCase();
svg.appendChild(svgText);
container.appendChild(svg);
}
}
// Toujours ajouter le badge de notification
container.appendChild(createStatBadge(number));
statBtn.appendChild(container);
});
colLeftestStats.dataset.badgesTransformed = '1';
}
function ensureEditorClasses() {
const selectors = [
'[id^="ajax-"] form#msg', '[id^="ajax-"] textarea',
'.col-sm-10 form#msg', '.col-sm-10 textarea#message',
'form[name="post_msg"]', 'textarea#message'
];
document.querySelectorAll(selectors.join(',')).forEach(el => {
const root = el.tagName?.toLowerCase() === 'form' ? el : (el.closest('form') || el.parentElement);
if (root && !root.classList.contains('editeur-text')) {
root.classList.add('editeur-text');
}
});
}
function ensurePageScoping() {
const isMembers = location?.pathname?.indexOf('/communaute/membres') === 0;
document.documentElement.classList.toggle('kr-page-members', isMembers);
}
function ensurePlayerMainPanelRows() {
const panel = document.getElementById('player-main-panel');
if (!panel) {return;}
Array.from(panel.children)
.filter(child => child.tagName?.toLowerCase() === 'div')
.filter(div => !div.classList.contains('kr-quick-access-buttons'))
.forEach(div => {
if (!div.classList.contains('row')) {div.classList.add('row');}
});
}
function addQuickAccessButtons() {
const panel = document.getElementById('player-main-panel');
if (!panel) {return;}
// Vérifier si les boutons ont déjà été ajoutés
if (panel.querySelector('.kr-quick-access-buttons')) {return;}
// Définir les boutons
const buttons = [
{ label: 'Agir', url: '/jouer/plateau', icon: 'fa-map' },
{ label: 'Matériel', url: '/jouer/materiel', icon: 'fa-box' },
{ label: 'Personnage', url: '/jouer/perso', icon: 'fa-user' },
{ label: 'Bâtiments', url: '/jouer/bat', icon: 'fa-building' },
{ label: 'Employés', url: '/jouer/pnj', icon: 'fa-users' }
];
// Créer le conteneur principal avec le système de grille Bootstrap
const container = document.createElement('div');
container.className = 'kr-quick-access-buttons';
container.style.marginTop = '10px';
const row = document.createElement('div');
row.className = 'row';
// Créer chaque bouton avec une colonne Bootstrap (2 par ligne)
buttons.forEach(btn => {
const col = document.createElement('div');
col.className = 'col-xs-6 col-sm-6';
const link = document.createElement('a');
link.href = btn.url;
link.className = 'btn btn-default btn-block mini';
const icon = document.createElement('i');
icon.className = `fa ${btn.icon}`;
link.appendChild(icon);
link.appendChild(document.createTextNode(' ' + btn.label));
col.appendChild(link);
row.appendChild(col);
});
container.appendChild(row);
// Ajouter les boutons à la fin du panneau
panel.appendChild(container);
}
// ============================================================================
// UI CONTROLS
// ============================================================================
function insertToggleCSSButton() {
if (document.getElementById('kr-toggle-css-btn')) {return;}
const mapBtn = Array.from(document.querySelectorAll('a'))
.find(a => a.getAttribute('onclick')?.includes('openMap'));
if (!mapBtn) {return;}
const mapLi = mapBtn.closest('li');
if (!mapLi?.parentElement) {return;}
const newLi = document.createElement('li');
const toggleBtn = document.createElement('a');
toggleBtn.href = '';
toggleBtn.id = 'kr-toggle-css-btn';
toggleBtn.innerHTML = ' ';
function updateTitle() {
toggleBtn.title = isThemeEnabled()
? 'Désactiver la surcharge CSS'
: 'Activer la surcharge CSS';
}
updateTitle();
toggleBtn.addEventListener('click', e => {
e.preventDefault();
if (isThemeEnabled()) {
applyThemeVariant('disable');
} else {
applyThemeVariant(getVariant());
}
return false;
});
newLi.appendChild(toggleBtn);
if (mapLi.parentElement) {
mapLi.parentElement.insertBefore(newLi, mapLi);
}
}
function insertTampermonkeyThemeUI() {
if (!location?.href?.includes('/profil/interface')) {return;}
function tryInsert() {
const headings = Array.from(document.querySelectorAll('h4, h3, h2'));
const target = headings.find(h =>
h.textContent?.trim().toLowerCase().includes('thème de base')
);
if (!target) {return false;}
if (document.getElementById('kr-tamper-theme')) {return true;}
const themeOptions = [
{ value: 'disable', flag: 'f0', label: 'Désactiver la surcharge CSS' },
{ value: 'kraland', flag: 'f1', label: 'République de Kraland' },
{ value: 'empire-brun', flag: 'f2', label: 'Empire Brun' },
{ value: 'empire-brun-dark', flag: 'f2', label: 'Empire Brun (Dark Mode)' },
{ value: 'paladium', flag: 'f3', label: 'Paladium Corporation' },
{ value: 'paladium-dark', flag: 'f3', label: 'Paladium Corporation (Dark Mode)' },
{ value: 'theocratie-seelienne', flag: 'f4', label: 'Théocratie Seelienne' },
{ value: 'theocratie-seelienne-dark', flag: 'f4', label: 'Théocratie Seelienne (Dark Mode)' },
{ value: 'paradigme-vert', flag: 'f5', label: 'Paradigme Vert' },
{ value: 'paradigme-vert-dark', flag: 'f5', label: 'Paradigme Vert (Dark Mode)' },
{ value: 'khanat-elmerien', flag: 'f6', label: 'Khanat Elmérien' },
{ value: 'khanat-elmerien-dark', flag: 'f6', label: 'Khanat Elmérien (Dark Mode)' },
{ value: 'confederation-libre', flag: 'f7', label: 'Confédération Libre' },
{ value: 'confederation-libre-dark', flag: 'f7', label: 'Confédération Libre (Dark Mode)' },
{ value: 'royaume-ruthvenie', flag: 'f8', label: 'Royaume de Ruthvénie' },
{ value: 'royaume-ruthvenie-dark', flag: 'f8', label: 'Royaume de Ruthvénie (Dark Mode)' }
];
const radios = themeOptions.map(opt => `
`).join('');
const statsDisplayRadios = `
Icônes
Texte
`;
const hideQuoteCheckbox = `
Masquer la citation du footer
`;
const container = document.createElement('div');
container.id = 'kr-tamper-theme';
container.className = 'well kr-tamper-theme';
container.innerHTML = `
Thème Tampermonkey (Activez le thème de base officiel pour éviter les conflits)
`;
if (target.parentElement) {
target.parentElement.insertBefore(container, target);
}
const form = container.querySelector('#kr-tamper-theme-form');
function syncUI() {
if (!isThemeEnabled()) {
const d = form.querySelector('input[value="disable"]');
if (d) {d.checked = true;}
} else {
const v = getVariant();
const el = form.querySelector(`input[value="${v}"]`);
if (el) {el.checked = true;}
}
// Synchroniser l'affichage des caractéristiques
const statsMode = getStatsDisplayMode();
const statsEl = form.querySelector(`input[name="kr-stats-display"][value="${statsMode}"]`);
if (statsEl) {statsEl.checked = true;}
// Synchroniser l'option de masquage de la citation
const hideQuote = localStorage.getItem('kr-hide-footer-quote') === 'true';
const hideQuoteEl = form.querySelector('#kr-hide-quote');
if (hideQuoteEl) {hideQuoteEl.checked = hideQuote;}
// Synchroniser l'option Carte médiévale
const medieval = localStorage.getItem(CONFIG.MEDIEVAL_MAP_KEY) === 'true';
const medievalEl = form.querySelector('#kr-medieval-map-checkbox');
if (medievalEl) { medievalEl.checked = medieval; }
}
form.addEventListener('change', (e) => {
// Gestion du changement de thème
if (e.target.name === 'kr-theme') {
const sel = form.querySelector('input[name="kr-theme"]:checked');
if (!sel) {return;}
const val = sel.value;
const feedback = document.createElement('div');
feedback.className = 'alert alert-success';
feedback.textContent = val === 'disable'
? 'Désactivation du thème...'
: 'Application du thème: ' + val;
container.appendChild(feedback);
setTimeout(() => applyThemeVariant(val), 300);
}
// Gestion du changement d'affichage des caractéristiques
if (e.target.name === 'kr-stats-display') {
const sel = form.querySelector('input[name="kr-stats-display"]:checked');
if (!sel) {return;}
const val = sel.value;
localStorage.setItem(CONFIG.STATS_DISPLAY_KEY, val);
const feedback = document.createElement('div');
feedback.className = 'alert alert-success';
feedback.textContent = val === 'icon'
? 'Affichage en icônes activé. Rechargez la page pour voir les changements.'
: 'Affichage en texte activé. Rechargez la page pour voir les changements.';
container.appendChild(feedback);
setTimeout(() => {
feedback.remove();
}, 5000);
}
// Gestion du masquage de la citation
if (e.target.name === 'kr-hide-quote') {
const isChecked = e.target.checked;
localStorage.setItem('kr-hide-footer-quote', isChecked.toString());
// Appliquer immédiatement le changement
if (isChecked) {
document.documentElement.classList.add('kr-hide-footer-quote');
} else {
document.documentElement.classList.remove('kr-hide-footer-quote');
}
const feedback = document.createElement('div');
feedback.className = 'alert alert-success';
feedback.textContent = isChecked
? 'Citation du footer masquée.'
: 'Citation du footer affichée.';
container.appendChild(feedback);
setTimeout(() => {
feedback.remove();
}, 3000);
}
// Gestion de la Carte médiévale
if (e.target.name === 'kr-medieval-map') {
const isChecked = e.target.checked;
localStorage.setItem(CONFIG.MEDIEVAL_MAP_KEY, isChecked.toString());
const feedback = document.createElement('div');
feedback.className = 'alert alert-success';
feedback.textContent = isChecked ? 'Carte médiévale activée. Application...' : 'Carte médiévale désactivée.';
container.appendChild(feedback);
// Appliquer immédiatement
applyMedievalMapOption();
setTimeout(() => feedback.remove(), 3000);
}
});
syncUI();
return true;
}
if (!tryInsert()) {
let attempts = 0;
const id = setInterval(() => {
attempts++;
if (tryInsert() || attempts > 25) {clearInterval(id);}
}, 200);
}
}
// ============================================================================
// OBSERVERS
// ============================================================================
function startObservers() {
let domTransformationsApplied = false;
const mo = new MutationObserver(() => {
if (isThemeEnabled()) {
if (!document.getElementById(CONFIG.STYLE_ID)) {
applyThemeInline(CONFIG.BUNDLED_CSS).catch(() => {});
}
if (!domTransformationsApplied) {
applyDOMTransformations();
domTransformationsApplied = true;
}
}
// S'assurer que la carte médiévale est appliquée si l'option est active
safeCall(() => applyMedievalMapOption());
safeCall(insertToggleCSSButton);
});
if (document.documentElement || document) {
mo.observe(document.documentElement || document, { childList: true, subtree: true });
}
// SPA navigation
const wrap = orig => function () {
const ret = orig.apply(this, arguments);
setTimeout(() => ensureTheme(), 250);
return ret;
};
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', () => setTimeout(() => ensureTheme(), 250));
}
// ============================================================================
// MODAL PERSONNAGE MOBILE - INTERACTIONS UX/UI
// ============================================================================
/**
* Améliore l'UX mobile du modal de sélection personnage
* - Navigation carousel pour sélecteur personnages (prev/next)
* - Description collapsible dans le header
* - Tabs swipeable avec détection de geste
* - Indicateurs de navigation (dots)
* - Compteur de caractères pour textarea
* - Feedback tactile sur les interactions
*/
function initCharacterModalMobile() {
// Observer l'apparition des modals Bootbox
const modalObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
// Vérifier si c'est une modal Bootbox
if (node.nodeType === 1 && node.classList?.contains('bootbox')) {
setTimeout(() => {
enhanceCharacterModal(node);
}, 150);
}
});
});
});
// Vérifier que document.body existe avant d'utiliser le MutationObserver
if (document.body) {
modalObserver.observe(document.body, { childList: true, subtree: false });
}
}
/**
* Scroll automatiquement vers l'onglet actif dans les modals personnage
* Surveille les mises à jour AJAX et maintient le scroll sur le bon onglet
*/
function initModalTabScroll() {
// Observer les changements dans les modals pour détecter les mises à jour AJAX
const tabScrollObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
// Vérifier si c'est un changement de contenu dans une modal
const modal = mutation.target.closest('.bootbox.modal');
if (!modal) {return;}
// Chercher le panel-heading avec les tabs
const panelHeading = modal.querySelector('.panel.with-nav-tabs .panel-heading');
if (!panelHeading) {return;}
// Chercher l'onglet actif
const activeTab = panelHeading.querySelector('.nav-tabs > li.active');
if (!activeTab) {return;}
// Scroller vers l'onglet actif
setTimeout(() => {
const tabRect = activeTab.getBoundingClientRect();
const containerRect = panelHeading.getBoundingClientRect();
// Calculer la position de scroll pour centrer l'onglet
const scrollLeft = activeTab.offsetLeft - (containerRect.width / 2) + (tabRect.width / 2);
panelHeading.scrollTo({
left: Math.max(0, scrollLeft),
behavior: 'smooth'
});
}, 100);
});
});
// Observer le body pour détecter les changements dans les modals
// Vérifier que document.body existe avant d'utiliser le MutationObserver
if (document.body) {
tabScrollObserver.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
}
}
/**
* Fusionne les colonnes d'une row en une seule div pour le mode mobile
* Remplace les changements de div par des espaces
*/
function mergeColumnsInMobile(modal) {
if (!modal) {return;}
// Trouver tous les panneaux avec des colonnes Bootstrap
const panels = modal.querySelectorAll('.panel-info, .panel-primary, .panel-default');
panels.forEach(panel => {
// Cibler les lignes dans panel-heading, panel-body, panel-footer
const rows = panel.querySelectorAll('.panel-heading .row, .panel-body .row, .panel-actions .row, .panel-footer .row');
rows.forEach(row => {
// Récupérer toutes les colonnes de cette row
const columns = row.querySelectorAll('[class*="col-"]');
if (columns.length > 0) {
// Collecter le contenu de toutes les colonnes avec des espaces entre elles
const contents = [];
columns.forEach(col => {
const text = col.textContent.trim();
const html = col.innerHTML.trim();
// Si la colonne contient des éléments (pas juste du texte), garder le HTML
if (col.children.length > 0) {
contents.push(html);
} else if (text) {
contents.push(text);
}
});
// Créer une nouvelle div unique avec tout le contenu
const mergedDiv = document.createElement('div');
mergedDiv.className = 'col-xs-12 merged-columns';
mergedDiv.innerHTML = contents.join(' ');
// Supprimer toutes les colonnes existantes
columns.forEach(col => col.remove());
// Ajouter la div fusionnée
row.appendChild(mergedDiv);
}
});
});
}
/**
* Force le layout grid pour les actions de la modal d'ordre
* Contourne le display:flex de Bootstrap 3 qui empêche grid de fonctionner
*/
function forceOrderModalGridLayout(modal) {
if (!modal) {return;}
// Vérifier si c'est une modal d'ordre (avec .bootbox-confirm)
if (!modal.classList.contains('bootbox-confirm')) {return;}
// Forcer le .panel-heading en colonne pour empiler les grilles verticalement
const panelHeading = modal.querySelector('.panel-heading');
if (panelHeading) {
panelHeading.style.setProperty('display', 'block', 'important');
}
// Chercher tous les ul.nav-tabs dans .panel-heading
const navTabsElements = modal.querySelectorAll('.panel-heading ul.nav-tabs');
if (navTabsElements.length === 0) {return;}
console.log('[Order Modal] Forçage du layout grid pour', navTabsElements.length, 'nav-tabs');
// Appliquer les styles grid via JavaScript (contourne Bootstrap)
navTabsElements.forEach((ul, index) => {
// Force display grid avec !important via setProperty
ul.style.setProperty('display', 'grid', 'important');
// Tous les groupes en 2 colonnes - grille homogène continue
ul.style.setProperty('grid-template-columns', 'repeat(2, 1fr)', 'important');
ul.style.setProperty('gap', '12px', 'important');
ul.style.setProperty('padding-left', '0', 'important');
// Force l'alignement des grid items au début (gauche)
ul.style.setProperty('justify-items', 'start', 'important');
ul.style.setProperty('align-items', 'stretch', 'important');
// Grille continue - même espacement partout (pas de séparation visuelle)
ul.style.setProperty('margin-bottom', '12px', 'important');
ul.style.setProperty('border-bottom', 'none', 'important');
ul.style.setProperty('border-top', 'none', 'important');
ul.style.setProperty('padding-top', '0', 'important');
// Désactiver les pseudo-éléments clearfix de Bootstrap qui deviennent grid items
// Créer/mettre à jour un élément