ajout test sites sur rapport de désinfo

Этот коммит содержится в:
BartM82 2025-10-18 14:23:22 +00:00
родитель e2382bbb29
Коммит 0ae313ccfe
8 изменённых файлов: 6523 добавлений и 153 удалений

Просмотреть файл

@ -1,8 +1,126 @@
// Plugin DIMA - content.js - Version finale consolidée
// Plugin DIMA - content.js - Version consolidée
// Détection de manipulation cognitive - M82 Project
// Version: 3.0 Refactored with ContentExtractor
// Note: All dependencies are loaded via manifest.json in correct order
// ============================================================================
// PARTIE 1: DÉTECTION DE SITES SUSPECTS (NOUVEAU)
// ============================================================================
/**
* Vérifie si le site actuel est dans la liste des sites suspects
* Cette fonction est fournie par suspiciousSitesManager.js
* et fonctionne automatiquement dès le chargement de la page
*/
function checkCurrentSiteInSuspiciousList() {
const currentUrl = window.location.href;
// Utiliser la fonction fournie par suspiciousSitesManager.js
const result = checkSuspiciousSite(currentUrl);
if (result.isSuspicious) {
console.log('⚠️ DIMA: Site suspect détecté!');
console.log('Source:', result.siteInfo.source);
console.log('Raison:', result.siteInfo.reason);
console.log('Niveau de risque:', result.siteInfo.riskLevel);
// Afficher une alerte visuelle
showSuspiciousSiteAlert(result);
}
}
/**
* Affiche une alerte pour un site suspect
*/
function showSuspiciousSiteAlert(result) {
// Créer un bandeau d'alerte en haut de la page
const alertBanner = document.createElement('div');
alertBanner.id = 'dima-suspicious-site-alert';
alertBanner.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, ${result.riskConfig.color}, ${result.riskConfig.color}dd);
color: white;
padding: 15px 20px;
z-index: 999999;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
animation: slideDown 0.5s ease-out;
`;
alertBanner.innerHTML = `
<div style="display: flex; align-items: center; gap: 15px; flex: 1;">
<span style="font-size: 24px;">${result.riskConfig.icon}</span>
<div>
<div style="font-weight: bold; font-size: 16px; margin-bottom: 5px;">
${result.riskConfig.label} - ${result.siteInfo.source}
</div>
<div style="font-size: 14px; opacity: 0.95;">
${result.siteInfo.reason}
</div>
<a href="${result.siteInfo.reportUrl}" target="_blank"
style="color: white; text-decoration: underline; font-size: 13px; margin-top: 5px; display: inline-block;">
Consulter le rapport source
</a>
</div>
</div>
<button id="dima-close-alert" style="
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
">
Fermer
</button>
`;
// Animation CSS
const style = document.createElement('style');
style.textContent = `
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
#dima-close-alert:hover {
background: rgba(255,255,255,0.3) !important;
}
`;
document.head.appendChild(style);
// Ajouter au body
document.body.insertBefore(alertBanner, document.body.firstChild);
// Gérer la fermeture
document.getElementById('dima-close-alert').addEventListener('click', () => {
alertBanner.style.animation = 'slideDown 0.3s ease-out reverse';
setTimeout(() => alertBanner.remove(), 300);
});
// Ajuster le padding du body pour ne pas cacher le contenu
document.body.style.paddingTop = `${alertBanner.offsetHeight}px`;
}
// ============================================================================
// PARTIE 2: ANALYSE DIMA du site visité
// ============================================================================
// ===== CLASSE PRINCIPALE DIMA =====
class DIMAAnalyzer {
constructor() {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,169 @@
// DIMA - Template pour nouvelle base de données d'opération
// REMPLACEZ "OPERATION_NAME" par le nom de votre opération (ex: Doppelganger, Portal_Kombat, etc.)
/**
* INSTRUCTIONS D'UTILISATION
* ==========================
*
* 1. Copiez ce fichier et renommez-le (ex: Doppelganger.js)
* 2. Remplacez tous les "OPERATION_NAME" par le nom de l'opération
* 3. Remplissez les domaines dans le tableau
* 4. Chargez ce fichier AVANT suspiciousSites.js dans votre HTML
*
* EXEMPLE:
* <script src="data/Copycop.js"></script>
* <script src="data/Doppelganger.js"></script>
* <script src="data/suspiciousSites.js"></script>
*/
// Nom de la variable globale (à adapter selon votre opération)
// Exemples:
// - copycopDomains (déjà existant)
// - doppelgangerDomains
// - portalKombatDomains
// - yourOperationDomains
const OPERATION_NAMEDomains = [
// ===== EXEMPLE D'ENTRÉE =====
{
domain: "example-fake-news.com",
matchType: "exact", // "exact", "contains", ou "pattern"
reason: "Site identifié dans l'opération [NOM], diffusant de la désinformation ciblée",
source: "Nom de l'organisation source (ex: EU DisinfoLab, DFRLab, etc.)",
reportUrl: "https://lien-vers-le-rapport-complet.com",
identifiedDate: "2025-01-15", // Format: YYYY-MM-DD
riskLevel: "high", // "high", "medium", ou "low"
tags: [
"OPERATION_NAME", // Tag obligatoire : nom de l'opération
"Russie", // Origine géographique si connue
"USA", // Pays ciblé
"Anti-Ukraine", // Thématique
"Élections" // Type de campagne
]
},
// ===== AJOUTEZ VOS DOMAINES ICI =====
/*
// Template à copier pour chaque nouveau domaine:
{
domain: "votre-domaine.com",
matchType: "exact",
reason: "Description précise de la raison",
source: "Organisation source",
reportUrl: "https://...",
identifiedDate: "YYYY-MM-DD",
riskLevel: "high|medium|low",
tags: ["OPERATION_NAME", "tag1", "tag2"]
},
*/
];
// =============================================================================
// FONCTIONS UTILITAIRES (OPTIONNELLES)
// =============================================================================
/**
* Ces fonctions sont optionnelles mais recommandées pour faciliter
* l'utilisation de votre base de données indépendamment du gestionnaire principal
*/
// Filtrer par tag
function filterOPERATION_NAMEByTag(tag) {
return OPERATION_NAMEDomains.filter(d => d.tags.includes(tag));
}
// Filtrer par niveau de risque
function filterOPERATION_NAMEByRiskLevel(level) {
return OPERATION_NAMEDomains.filter(d => d.riskLevel === level);
}
// Obtenir tous les tags uniques
function getOPERATION_NAMETags() {
const allTags = new Set();
OPERATION_NAMEDomains.forEach(d => {
d.tags.forEach(tag => allTags.add(tag));
});
return Array.from(allTags).sort();
}
// Obtenir les statistiques
function getOPERATION_NAMEStats() {
return {
total: OPERATION_NAMEDomains.length,
highRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "high").length,
mediumRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "medium").length,
lowRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "low").length,
tags: getOPERATION_NAMETags()
};
}
// =============================================================================
// EXPORTS ET DISPONIBILITÉ GLOBALE
// =============================================================================
// Export pour Node.js / modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
OPERATION_NAMEDomains,
filterOPERATION_NAMEByTag,
filterOPERATION_NAMEByRiskLevel,
getOPERATION_NAMETags,
getOPERATION_NAMEStats
};
}
// Disponibilité globale pour le navigateur
if (typeof window !== 'undefined') {
window.OPERATION_NAMEDomains = OPERATION_NAMEDomains;
window.OPERATION_NAMEUtils = {
filterByTag: filterOPERATION_NAMEByTag,
filterByRiskLevel: filterOPERATION_NAMEByRiskLevel,
getTags: getOPERATION_NAMETags,
getStats: getOPERATION_NAMEStats
};
}
// Log de chargement
console.log(`Liste OPERATION_NAME chargée: ${OPERATION_NAMEDomains.length} domaines identifiés`);
if (OPERATION_NAMEDomains.length > 0) {
console.log("Statistiques OPERATION_NAME:", getOPERATION_NAMEStats());
}
// =============================================================================
// GUIDE DES TAGS RECOMMANDÉS
// =============================================================================
/**
* TAGS OBLIGATOIRES:
* - Le nom de votre opération (ex: "Doppelganger", "Portal_Kombat")
*
* TAGS GÉOGRAPHIQUES (origine):
* - Russie, Chine, Iran, Corée_du_Nord, etc.
*
* TAGS GÉOGRAPHIQUES (cible):
* - USA, France, Canada, UK, Allemagne, Ukraine, etc.
* - Sites-US, Sites-France, Sites-Canada (pour collections de sites locaux)
*
* TAGS THÉMATIQUES:
* - Anti-Ukraine
* - Élections
* - COVID-19
* - Climat
* - Immigration
* - Santé
*
* TAGS TECHNIQUES:
* - LLM (contenu généré par IA)
* - Deepfake
* - Usurpation-Identité
* - Bot-Network
* - Infrastructure
*
* TAGS DE MÉTHODE:
* - Désinformation-Ciblée
* - Amplification-Artificielle
* - Multi-Langues
* - Coordination-Cross-Platform
*/

Просмотреть файл

@ -1,23 +1,37 @@
{
"manifest_version": 3,
"name": "Analyseur DIMA - M82 Project",
"version": "1.1",
"description": "Plugin d'analyse de manipulation cognitive selon la matrice DIMA par M82 Project",
"permissions": ["activeTab", "storage"],
"name": "DIMA - Digital Influence Manipulation Analyzer",
"version": "2.0.0",
"description": "Plugin d'analyse de manipulation cognitive selon la matrice DIMA par M82 Project, détecte et analyse les sites suspects identifiés dans des rapports de désinformation",
"permissions": [
"activeTab",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": [
"data/databases/Copycop.js",
"data/databases/PortalKombat.js",
"data/databases/RRN.js",
"data/techniques.js",
"data/keywords.js",
"modules/contentExtractor.js",
"modules/suspiciousSitesManager.js",
"modules/techniqueAnalyzer.js",
"modules/uiManager.js",
"content.js"
]
],
"run_at": "document_end"
}
],
"action": {
"action": {
"default_title": "Analyse DIMA - M82 Project"
},
"icons": {

Просмотреть файл

@ -0,0 +1,310 @@
// DIMA - Gestionnaire Central de Sites Suspects
// Ce fichier charge et agrège toutes les bases de données de domaines suspects
/**
* Gestionnaire centralisé des sites suspects
* Charge automatiquement toutes les bases de données disponibles
* et fournit une API unifiée pour vérifier les sites
*/
class SuspiciousSitesManager {
constructor() {
this.sources = new Map();
this.allSites = [];
this.stats = {
totalSites: 0,
byRiskLevel: { high: 0, medium: 0, low: 0 },
bySources: {},
byTags: {}
};
this.init();
}
/**
* Initialise le gestionnaire en chargeant toutes les sources disponibles
*/
init() {
console.log('🛡️ DIMA: Initialisation du gestionnaire de sites suspects...');
// Détecter et charger les sources disponibles
this.detectAndLoadSources();
// Agréger tous les sites
this.aggregateAllSites();
// Calculer les statistiques
this.calculateStats();
console.log(`✅ DIMA: ${this.allSites.length} sites suspects chargés depuis ${this.sources.size} source(s)`);
this.logStats();
}
/**
* Détecte et charge automatiquement toutes les sources disponibles
*/
detectAndLoadSources() {
// Source 1: CopyCop (Recorded Future)
if (typeof copycopDomains !== 'undefined' && Array.isArray(copycopDomains)) {
this.registerSource('CopyCop', copycopDomains, {
name: 'Opération CopyCop',
description: 'Réseau russe de sites fictifs et de désinformation',
organization: 'Recorded Future - Insikt Group',
reportUrl: 'https://www.recordedfuture.com/research/cta-ru-2025-0917',
reportDate: '2025-09-17'
});
console.log(` ✓ Source CopyCop chargée: ${copycopDomains.length} domaines`);
}
// Source 2: RRN (VIGINUM)
if (typeof rrnDomains !== 'undefined' && Array.isArray(rrnDomains)) {
this.registerSource('RRN', rrnDomains, {
name: 'Réseau RRN',
description: 'Réseau de faux médias et infrastructure de désinformation pro-russe',
organization: 'VIGINUM',
reportUrl: 'https://www.sgdsn.gouv.fr/files/files/20230619_NP_VIGINUM_RAPPORT-CAMPAGNE-RRN_VF_0.pdf',
reportDate: '2023-06-19'
});
console.log(` ✓ Source RRN chargée: ${rrnDomains.length} domaines`);
}
// Source 3: Doppelganger (à venir)
if (typeof doppelgangerDomains !== 'undefined' && Array.isArray(doppelgangerDomains)) {
this.registerSource('Doppelganger', doppelgangerDomains, {
name: 'Opération Doppelganger',
description: 'Sites usurpant l\'identité de médias légitimes',
organization: 'À définir',
reportUrl: '',
reportDate: ''
});
console.log(` ✓ Source Doppelganger chargée: ${doppelgangerDomains.length} domaines`);
}
// Source 4: Portal Kombat (à venir)
if (typeof portalKombatDomains !== 'undefined' && Array.isArray(portalKombatDomains)) {
this.registerSource('PortalKombat', portalKombatDomains, {
name: 'Opération Portal Kombat',
description: 'Réseau d\'influence',
organization: 'À définir',
reportUrl: '',
reportDate: ''
});
console.log(` ✓ Source Portal Kombat chargée: ${portalKombatDomains.length} domaines`);
}
// Avertissement si aucune source n'est chargée
if (this.sources.size === 0) {
console.warn('⚠️ DIMA: Aucune base de données de sites suspects n\'a été chargée');
console.warn(' Vérifiez que les fichiers de bases de données sont correctement chargés avant ce gestionnaire');
}
}
/**
* Enregistre une nouvelle source de données
*/
registerSource(sourceName, domains, metadata) {
this.sources.set(sourceName, {
domains: domains,
metadata: metadata,
count: domains.length
});
}
/**
* Agrège tous les sites de toutes les sources
*/
aggregateAllSites() {
this.allSites = [];
for (const [sourceName, sourceData] of this.sources) {
this.allSites.push(...sourceData.domains);
}
}
/**
* Calcule les statistiques globales
*/
calculateStats() {
this.stats.totalSites = this.allSites.length;
// Reset stats
this.stats.byRiskLevel = { high: 0, medium: 0, low: 0 };
this.stats.bySources = {};
this.stats.byTags = {};
// Compter par niveau de risque et tags
this.allSites.forEach(site => {
// Par niveau de risque
if (site.riskLevel) {
this.stats.byRiskLevel[site.riskLevel] = (this.stats.byRiskLevel[site.riskLevel] || 0) + 1;
}
// Par source
if (site.source) {
this.stats.bySources[site.source] = (this.stats.bySources[site.source] || 0) + 1;
}
// Par tags
if (site.tags && Array.isArray(site.tags)) {
site.tags.forEach(tag => {
this.stats.byTags[tag] = (this.stats.byTags[tag] || 0) + 1;
});
}
});
}
/**
* Affiche les statistiques dans la console
*/
logStats() {
console.log('📊 Statistiques:');
console.log(` Total: ${this.stats.totalSites} sites`);
console.log(` Risque élevé: ${this.stats.byRiskLevel.high || 0}`);
console.log(` Risque moyen: ${this.stats.byRiskLevel.medium || 0}`);
console.log(` Risque faible: ${this.stats.byRiskLevel.low || 0}`);
console.log(` Sources: ${Object.keys(this.stats.bySources).length}`);
}
/**
* Vérifie si une URL correspond à un site suspect
* @param {string} url - L'URL à vérifier
* @returns {Object} Résultat de la vérification
*/
checkSite(url) {
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
for (const site of this.allSites) {
let isMatch = false;
switch (site.matchType) {
case "exact":
isMatch = hostname === site.domain.toLowerCase() ||
hostname === `www.${site.domain.toLowerCase()}`;
break;
case "contains":
isMatch = hostname.includes(site.domain.toLowerCase());
break;
case "pattern":
try {
const regex = new RegExp(site.domain, "i");
isMatch = regex.test(hostname);
} catch (e) {
console.error(`DIMA: Pattern regex invalide pour ${site.domain}:`, e);
}
break;
}
if (isMatch) {
return {
isSuspicious: true,
siteInfo: site,
riskConfig: this.getRiskConfig(site.riskLevel),
matchedHostname: hostname
};
}
}
return { isSuspicious: false };
} catch (error) {
console.error("DIMA: Erreur lors de la vérification du site suspect:", error);
return { isSuspicious: false, error: error.message };
}
}
/**
* Retourne la configuration visuelle pour un niveau de risque
*/
getRiskConfig(riskLevel) {
const RISK_LEVELS = {
high: {
color: "#c0392b",
icon: "⚠️",
label: "Risque Élevé",
message: "Ce site a été identifié comme diffusant de la désinformation de manière systématique."
},
medium: {
color: "#e67e22",
icon: "⚡",
label: "Vigilance Requise",
message: "Ce site a été signalé pour des pratiques douteuses."
},
low: {
color: "#f39c12",
icon: "",
label: "À Surveiller",
message: "Ce site présente des caractéristiques suspectes."
}
};
return RISK_LEVELS[riskLevel] || RISK_LEVELS.low;
}
/**
* Retourne les statistiques
*/
getStats() {
return this.stats;
}
/**
* Retourne les informations sur toutes les sources chargées
*/
getSourcesInfo() {
const sourcesInfo = [];
for (const [sourceName, sourceData] of this.sources) {
sourcesInfo.push({
name: sourceName,
count: sourceData.count,
...sourceData.metadata
});
}
return sourcesInfo;
}
/**
* Recherche des sites par tag
*/
searchByTag(tag) {
return this.allSites.filter(site =>
site.tags && site.tags.includes(tag)
);
}
/**
* Recherche des sites par source
*/
searchBySource(sourceName) {
return this.allSites.filter(site =>
site.source === sourceName
);
}
}
// Initialisation automatique du gestionnaire
let suspiciousSitesManager;
// Initialiser après le chargement de toutes les bases de données
if (typeof window !== 'undefined') {
// Dans le navigateur, initialiser après un court délai pour laisser les autres fichiers se charger
setTimeout(() => {
suspiciousSitesManager = new SuspiciousSitesManager();
// Rendre disponible globalement
window.suspiciousSitesManager = suspiciousSitesManager;
// Pour compatibilité avec l'ancien code, exposer aussi checkSuspiciousSite
window.checkSuspiciousSite = (url) => suspiciousSitesManager.checkSite(url);
// Exposer aussi les statistiques et infos
window.getSuspiciousSitesStats = () => suspiciousSitesManager.getStats();
window.getSuspiciousSitesSourcesInfo = () => suspiciousSitesManager.getSourcesInfo();
}, 100);
}
// Export pour Node.js si nécessaire
if (typeof module !== 'undefined' && module.exports) {
module.exports = SuspiciousSitesManager;
}

Просмотреть файл

@ -9,6 +9,7 @@ class UIManager {
this.buttonCreated = false;
this.analysisResults = null;
this.pageType = 'general';
this.suspiciousSiteCheck = null;
}
log(message, data = null) {
@ -30,12 +31,20 @@ class UIManager {
console.error('DIMA: Aucun résultat d\'analyse disponible pour créer le bouton');
return;
}
// Vérifier si le site est suspect
this.suspiciousSiteCheck = window.checkSuspiciousSite ?
window.checkSuspiciousSite(window.location.href) :
{ isSuspicious: false };
try {
// Supprimer bouton existant
document.getElementById('dima-btn')?.remove();
document.getElementById('dima-suspicious-alert')?.remove();
if (this.buttonCreated) return;
// Créer le bouton principal
const button = document.createElement('div');
button.id = 'dima-btn';
@ -80,6 +89,12 @@ class UIManager {
});
document.body?.appendChild(button);
// Créer l'alerte de site suspect si nécessaire
if (this.suspiciousSiteCheck.isSuspicious) {
this.createSuspiciousSiteAlert();
}
this.buttonCreated = true;
this.log('Bouton créé avec succès');
@ -88,6 +103,227 @@ class UIManager {
}
}
createSuspiciousSiteAlert() {
const { siteInfo, riskConfig } = this.suspiciousSiteCheck;
const alert = document.createElement('div');
alert.id = 'dima-suspicious-alert';
alert.innerHTML = `
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 24px;">${riskConfig.icon}</span>
<div style="flex: 1;">
<div style="font-weight: bold; margin-bottom: 4px; font-size: 14px;">
${riskConfig.label}
</div>
<div style="font-size: 12px; line-height: 1.4; margin-bottom: 8px;">
Vigilance : ce site appartient à un dispositif de manipulation de l'information identifié.
</div>
<button id="dima-suspicious-details" style="
background: white;
color: ${riskConfig.color};
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 11px;
font-weight: 600;
transition: all 0.2s;
">
En savoir plus
</button>
</div>
<button id="dima-suspicious-close" style="
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 20px;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity 0.2s;
">
×
</button>
</div>
`;
alert.style.cssText = `
position: fixed !important;
top: 70px !important;
right: 20px !important;
z-index: 999998 !important;
background: linear-gradient(135deg, ${riskConfig.color}, ${this.adjustColor(riskConfig.color, -15)}) !important;
color: white !important;
padding: 16px !important;
border-radius: 12px !important;
max-width: 350px !important;
font-family: 'Segoe UI', Arial, sans-serif !important;
box-shadow: 0 6px 20px rgba(0,0,0,0.3) !important;
border: 2px solid rgba(255,255,255,0.2) !important;
animation: slideInRight 0.4s ease-out !important;
backdrop-filter: blur(10px) !important;
`;
document.body?.appendChild(alert);
// Événements
document.getElementById('dima-suspicious-details')?.addEventListener('click', () => {
this.showSuspiciousSiteDetails();
});
document.getElementById('dima-suspicious-close')?.addEventListener('click', () => {
alert.remove();
});
// Hover effects
const detailsBtn = document.getElementById('dima-suspicious-details');
if (detailsBtn) {
detailsBtn.addEventListener('mouseenter', () => {
detailsBtn.style.transform = 'translateY(-1px)';
detailsBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
});
detailsBtn.addEventListener('mouseleave', () => {
detailsBtn.style.transform = 'translateY(0)';
detailsBtn.style.boxShadow = 'none';
});
}
const closeBtn = document.getElementById('dima-suspicious-close');
if (closeBtn) {
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.opacity = '1';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.opacity = '0.7';
});
}
}
showSuspiciousSiteDetails() {
const { siteInfo, riskConfig } = this.suspiciousSiteCheck;
// Créer modal avec détails
const detailsModal = document.createElement('div');
detailsModal.id = 'dima-suspicious-details-modal';
detailsModal.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0,0,0,0.75) !important;
backdrop-filter: blur(5px) !important;
z-index: 10000000 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-family: 'Segoe UI', Arial, sans-serif !important;
animation: fadeIn 0.3s ease-out !important;
`;
const logoUrl = chrome.runtime.getURL('M82-logo-16.png');
detailsModal.innerHTML = `
<div style="background: white; padding: 30px; border-radius: 20px; max-width: 600px; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 25px 50px rgba(0,0,0,0.3); animation: slideIn 0.3s ease-out;">
<!-- En-tête -->
<div style="text-align: center; margin-bottom: 25px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0;">
<div style="display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 10px;">
<img src="${logoUrl}"
style="width: 24px; height: 24px;"
alt="M82 Project"
onerror="this.style.display='none'">
<h2 style="color: #2c3e50; margin: 0; font-size: 1.8em;">Site Suspect Identifié</h2>
</div>
<div style="display: inline-block; background: ${riskConfig.color}; color: white; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; margin-top: 10px;">
${riskConfig.icon} ${riskConfig.label}
</div>
</div>
<!-- Contenu -->
<div style="margin-bottom: 25px;">
<div style="background: linear-gradient(135deg, #fff3cd, #ffeaa7); padding: 20px; border-radius: 12px; border-left: 4px solid ${riskConfig.color}; margin-bottom: 20px;">
<h3 style="margin: 0 0 10px 0; color: #856404; font-size: 1.1em;"> Avertissement</h3>
<p style="margin: 0; color: #856404; line-height: 1.6;">
${riskConfig.message}
</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 15px;">
<h4 style="margin: 0 0 12px 0; color: #2c3e50; font-size: 1em;">📋 Détails de l'identification</h4>
<div style="display: grid; gap: 12px;">
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Raison :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${siteInfo.reason}</div>
</div>
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Source du rapport :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${siteInfo.source}</div>
</div>
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Date d'identification :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${new Date(siteInfo.identifiedDate).toLocaleDateString('fr-FR')}</div>
</div>
${siteInfo.tags && siteInfo.tags.length > 0 ? `
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Catégories :</strong>
<div style="display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;">
${siteInfo.tags.map(tag => `
<span style="background: #e9ecef; color: #495057; padding: 4px 10px; border-radius: 12px; font-size: 0.8em;">
${tag}
</span>
`).join('')}
</div>
</div>
` : ''}
</div>
</div>
<div style="background: #e8f4f8; padding: 16px; border-radius: 10px; border-left: 4px solid #17a2b8;">
<h4 style="margin: 0 0 8px 0; color: #0c5460; font-size: 0.95em;">💡 Recommandations</h4>
<ul style="margin: 0; padding-left: 20px; color: #0c5460; line-height: 1.6;">
<li>Vérifiez les informations auprès de sources fiables</li>
<li>Consultez plusieurs sources avant de partager</li>
<li>Soyez attentif aux techniques de manipulation détectées</li>
<li>Signalez le contenu suspect si nécessaire</li>
</ul>
</div>
</div>
<!-- Actions -->
<div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
<button onclick="window.open('${siteInfo.reportUrl}', '_blank')"
style="background: #3498db; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 500; transition: all 0.3s; box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);">
📄 Consulter le rapport complet
</button>
<button onclick="document.getElementById('dima-suspicious-details-modal').remove()"
style="background: #95a5a6; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 500; transition: all 0.3s;">
Fermer
</button>
</div>
<div style="text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #e9ecef; color: #7f8c8d; font-size: 0.85em;">
Base de données maintenue par
<a href="https://m82-project.org/" target="_blank"
style="color: #3498db; text-decoration: none; font-weight: 500;">M82 Project</a>
</div>
</div>
`;
detailsModal.addEventListener('click', (e) => {
if (e.target === detailsModal) detailsModal.remove();
});
document.body.appendChild(detailsModal);
}
adjustColor(color, amount) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * amount);
@ -101,116 +337,205 @@ class UIManager {
generateTooltip() {
const techniques = this.analysisResults.detectedTechniques.slice(0, 3);
return `DIMA Score: ${this.analysisResults.globalScore} (${this.analysisResults.riskLevel})
let tooltip = `DIMA Score: ${this.analysisResults.globalScore} (${this.analysisResults.riskLevel})
${this.analysisResults.detectedTechniques.length} techniques détectées
${techniques.map(t => `${t.nom}`).join('\n')}
Contenu: ${this.analysisResults.contentLength} caractères`;
}
getScoreIcon(score) {
if (score >= 75) return '🚨'; // Critique
if (score >= 50) return '⚠️'; // Élevé
if (score >= 30) return '⚡'; // Modéré
if (score >= 15) return '👀'; // Faible
return '✅'; // Très faible
}
generateExecutiveSummary() {
const score = this.analysisResults.globalScore;
const techniqueCount = this.analysisResults.detectedTechniques.length;
const topTechniques = this.analysisResults.detectedTechniques.slice(0, 3);
let summary = "";
// Évaluation générale selon le score
if (score >= 75) {
summary = `🚨 <strong>Manipulation intensive détectée</strong> : Ce contenu présente un niveau critique de techniques manipulatoires (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else if (score >= 50) {
summary = `⚠️ <strong>Manipulation significative</strong> : Ce contenu utilise plusieurs techniques suspectes (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else if (score >= 30) {
summary = `⚡ <strong>Éléments manipulatoires présents</strong> : Quelques techniques détectées nécessitent votre attention (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else {
summary = `👀 <strong>Faible niveau de manipulation</strong> : Peu d'éléments manipulatoires détectés (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
}
// Analyse des phases dominantes
if (this.analysisResults.phaseScores && Object.keys(this.analysisResults.phaseScores).length > 0) {
const sortedPhases = Object.entries(this.analysisResults.phaseScores)
.sort(([,a], [,b]) => b - a)
.slice(0, 2);
${techniques.map(t => `${t.nom}`).join('\n')}`;
if (sortedPhases.length > 0) {
const dominantPhase = sortedPhases[0][0];
summary += `La manipulation se concentre principalement sur la phase "<strong>${dominantPhase}</strong>" (${this.getPhaseExplanation(dominantPhase)}). `;
if (this.suspiciousSiteCheck.isSuspicious) {
tooltip += `\n\n⚠️ SITE SUSPECT IDENTIFIÉ`;
}
}
// Technique principale
if (topTechniques.length > 0) {
const mainTechnique = topTechniques[0];
summary += `La technique dominante est <strong>${mainTechnique.nom}</strong> avec ${mainTechnique.confidence}% de confiance. `;
// Conseil spécifique selon la technique
summary += this.getTechniqueAdvice(mainTechnique.index);
}
return summary;
tooltip += `\nContenu: ${this.analysisResults.contentLength} caractères`;
return tooltip;
}
getPhaseEmoji(phase) {
const emojis = {
'Detect': '👁️',
'Informer': '📢',
'Mémoriser': '🧠',
'Act': '⚡'
};
return emojis[phase] || '📍';
generatePhaseAnalysis() {
if (!this.analysisResults || !this.analysisResults.detectedTechniques || this.analysisResults.detectedTechniques.length === 0) {
return '';
}
// Analyser les techniques par phase
const phaseStats = {
'Detect': { count: 0, totalScore: 0, techniques: [], icon: '👁️', color: '#3498db' },
'Informer': { count: 0, totalScore: 0, techniques: [], icon: '📢', color: '#e67e22' },
'Mémoriser': { count: 0, totalScore: 0, techniques: [], icon: '🧠', color: '#9b59b6' },
'Agir': { count: 0, totalScore: 0, techniques: [], icon: '⚡', color: '#e74c3c' }
};
this.analysisResults.detectedTechniques.forEach(technique => {
const phase = technique.phase || 'Detect';
if (phaseStats[phase]) {
phaseStats[phase].count++;
phaseStats[phase].totalScore += technique.weightedScore || technique.score || 0;
phaseStats[phase].techniques.push(technique);
}
});
// Calculer les pourcentages
const totalTechniques = this.analysisResults.detectedTechniques.length;
const totalScore = Object.values(phaseStats).reduce((sum, phase) => sum + phase.totalScore, 0);
// Trouver la phase dominante
let dominantPhase = null;
let maxCount = 0;
Object.entries(phaseStats).forEach(([phase, stats]) => {
if (stats.count > maxCount) {
maxCount = stats.count;
dominantPhase = phase;
}
});
// Générer l'explication contextuelle
const explanation = this.generatePhaseExplanation(dominantPhase, phaseStats, totalTechniques);
// Générer le HTML
return `
<div style="background: #f8f9fa; padding: 25px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
<h3 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 1.2em; display: flex; align-items: center; gap: 10px;">
📊 Analyse par Phase DIMA
<span style="font-size: 0.7em; color: #7f8c8d; font-weight: normal; font-style: italic;">
(Detect, Informer, Mémoriser, Agir)
</span>
</h3>
<!-- Explication contextuelle -->
<div style="background: linear-gradient(135deg, #e8f4f8, #d4e8f0); padding: 16px; border-radius: 10px; margin-bottom: 20px; border-left: 4px solid ${phaseStats[dominantPhase]?.color || '#3498db'};">
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 24px;">${phaseStats[dominantPhase]?.icon || '💡'}</span>
<div>
<h4 style="margin: 0 0 8px 0; color: #0c5460; font-size: 1em;">
Analyse : Phase dominante "${dominantPhase}"
</h4>
<p style="margin: 0; color: #0c5460; font-size: 0.9em; line-height: 1.5;">
${explanation}
</p>
</div>
</div>
</div>
<!-- Répartition visuelle des phases -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 20px;">
${Object.entries(phaseStats).map(([phase, stats]) => {
const percentage = totalTechniques > 0 ? Math.round((stats.count / totalTechniques) * 100) : 0;
const scorePercentage = totalScore > 0 ? Math.round((stats.totalScore / totalScore) * 100) : 0;
const isActive = stats.count > 0;
return `
<div style="
background: ${isActive ? 'white' : '#f8f9fa'};
padding: 15px;
border-radius: 10px;
text-align: center;
border: ${isActive ? `2px solid ${stats.color}` : '1px solid #e9ecef'};
opacity: ${isActive ? '1' : '0.5'};
transition: all 0.3s;
">
<div style="font-size: 24px; margin-bottom: 8px;">${stats.icon}</div>
<div style="font-weight: bold; color: ${stats.color}; font-size: 0.85em; margin-bottom: 4px;">
${phase}
</div>
<div style="font-size: 1.8em; font-weight: bold; color: ${isActive ? stats.color : '#bdc3c7'}; margin-bottom: 4px;">
${stats.count}
</div>
<div style="font-size: 0.75em; color: #7f8c8d; margin-bottom: 8px;">
${percentage}% techniques
</div>
${isActive ? `
<div style="background: ${stats.color}20; padding: 4px 8px; border-radius: 6px; font-size: 0.7em; color: ${stats.color}; font-weight: 600;">
${scorePercentage}% du score
</div>
` : ''}
</div>
`;
}).join('')}
</div>
<!-- Graphique à barres -->
<div style="background: white; padding: 15px; border-radius: 10px; border: 1px solid #e9ecef;">
<h4 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 0.95em;">Distribution du score par phase</h4>
${Object.entries(phaseStats).map(([phase, stats]) => {
const percentage = totalScore > 0 ? (stats.totalScore / totalScore) * 100 : 0;
const displayScore = stats.totalScore.toFixed(1);
return `
<div style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<span style="font-size: 0.85em; font-weight: 600; color: #2c3e50;">
${stats.icon} ${phase}
</span>
<span style="font-size: 0.8em; color: #7f8c8d;">
${displayScore} pts (${Math.round(percentage)}%)
</span>
</div>
<div style="background: #e9ecef; height: 8px; border-radius: 4px; overflow: hidden;">
<div style="
background: linear-gradient(90deg, ${stats.color}, ${this.adjustColor(stats.color, -15)});
width: ${percentage}%;
height: 100%;
transition: width 0.6s ease-out;
border-radius: 4px;
"></div>
</div>
</div>
`;
}).join('')}
</div>
<!-- Comprendre les phases -->
<div style="margin-top: 20px; padding: 15px; background: white; border-radius: 10px; border: 1px solid #e9ecef;">
<details style="cursor: pointer;">
<summary style="font-weight: 600; color: #2c3e50; font-size: 0.9em; padding: 5px; outline: none;">
Comprendre les phases DIMA
</summary>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e9ecef; font-size: 0.85em; line-height: 1.6; color: #555;">
<div style="margin-bottom: 10px;">
<strong style="color: #3498db;">👁 Detect (Détecter)</strong> :
Techniques visant à capter l'attention et identifier les cibles sensibles aux messages.
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #e67e22;">📢 Informer</strong> :
Techniques de transmission et cadrage de l'information pour influencer la perception.
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #9b59b6;">🧠 Mémoriser</strong> :
Techniques d'ancrage mémoriel et de renforcement des messages dans la durée.
</div>
<div>
<strong style="color: #e74c3c;"> Agir</strong> :
Techniques d'incitation à l'action et de mobilisation comportementale.
</div>
</div>
</details>
</div>
</div>
`;
}
getPhaseColor(phase) {
const colors = {
'Detect': '#e3f2fd', // Bleu clair
'Informer': '#f3e5f5', // Violet clair
'Mémoriser': '#e8f5e8', // Vert clair
'Act': '#fff3e0' // Orange clair
};
return colors[phase] || '#f5f5f5';
generatePhaseExplanation(dominantPhase, phaseStats, totalTechniques) {
const explanations = {
'Detect': `Le contenu utilise principalement des techniques de <strong>détection et captation d'attention</strong> (${phaseStats['Detect'].count}/${totalTechniques} techniques). Cela suggère une stratégie axée sur l'identification des publics réceptifs et l'accroche initiale. Le contenu cherche à attirer et cibler des audiences spécifiques.`,
'Informer': `Le contenu se concentre sur des techniques de <strong>transmission et cadrage de l'information</strong> (${phaseStats['Informer'].count}/${totalTechniques} techniques). L'objectif est de contrôler la perception de l'information via le choix des faits présentés, leur contextualisation, et les biais introduits dans le message.`,
'Mémoriser': `Le contenu privilégie des techniques de <strong>mémorisation et ancrage</strong> (${phaseStats['Mémoriser'].count}/${totalTechniques} techniques). Ces méthodes visent à inscrire durablement les messages dans la mémoire du public, souvent par répétition, simplification ou associations émotionnelles fortes.`,
'Agir': `Le contenu met l'accent sur des techniques d'<strong>incitation à l'action</strong> (${phaseStats['Agir'].count}/${totalTechniques} techniques). L'objectif est de mobiliser le public vers des comportements spécifiques : partage, engagement, manifestation, ou modification d'opinions et de votes.`
};
// Si plusieurs phases sont également représentées
const topPhases = Object.entries(phaseStats)
.filter(([_, stats]) => stats.count > 0)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 2);
if (topPhases.length > 1 && topPhases[0][1].count === topPhases[1][1].count) {
return `Le contenu présente une <strong>stratégie équilibrée</strong> entre les phases "${topPhases[0][0]}" et "${topPhases[1][0]}" (${topPhases[0][1].count} techniques chacune). Cette combinaison indique une approche sophistiquée visant à la fois à attirer l'attention et à générer un impact durable.`;
}
return explanations[dominantPhase] || 'Analyse de la répartition des techniques de manipulation cognitive détectées selon le modèle DIMA.';
}
getPhaseDescription(phase) {
const descriptions = {
'Detect': 'Capter l\'attention',
'Informer': 'Influencer ou orienter la compréhension',
'Mémoriser': 'Ancrer l\'information',
'Act': 'Provoquer l\'action'
};
return descriptions[phase] || phase;
}
getPhaseExplanation(phase) {
const explanations = {
'Detect': 'techniques pour attirer et capter votre attention',
'Informer': 'méthodes pour orienter votre interprétation des faits',
'Mémoriser': 'stratégies pour ancrer certaines idées dans votre mémoire',
'Act': 'pressions pour vous pousser à agir rapidement'
};
return explanations[phase] || 'manipulation cognitive';
}
getTechniqueAdvice(techniqueIndex) {
const advices = {
'TE0500': 'Méfiez-vous des titres sensationnalistes et vérifiez les sources.',
'TE0132': 'Prenez du recul face aux messages alarmistes excessifs.',
'TE0501': 'Résistez à la pression de l\'urgence et prenez le temps de réfléchir.',
'TE0422': 'Vérifiez les qualifications réelles des "experts" cités.',
'TE0251': 'Questionnez les affirmations sur ce que "tout le monde" pense.',
'TE0221': 'Attention aux généralisations excessives sur des groupes.',
'TE0212': 'Ne tirez pas de conclusions générales à partir d\'anecdotes.',
'TE0321': 'Cherchez des sources contradictoires pour éviter le biais de confirmation.'
};
return advices[techniqueIndex] || 'Restez critique et vérifiez les informations.';
}
showModal() {
try {
this.log('Affichage du modal');
@ -235,9 +560,33 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
font-family: 'Segoe UI', Arial, sans-serif !important;
animation: fadeIn 0.3s ease-out !important;
`;
// Récupérer l'URL du logo
const logoUrl = chrome.runtime.getURL('M82-logo-16.png');
// Construire le contenu avec alerte site suspect si nécessaire
let suspiciousAlert = '';
if (this.suspiciousSiteCheck.isSuspicious) {
const { riskConfig, siteInfo } = this.suspiciousSiteCheck;
suspiciousAlert = `
<div style="background: linear-gradient(135deg, ${riskConfig.color}, ${this.adjustColor(riskConfig.color, -15)}); color: white; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 2px solid rgba(255,255,255,0.2);">
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 28px;">${riskConfig.icon}</span>
<div style="flex: 1;">
<h3 style="margin: 0 0 8px 0; font-size: 1.2em;">${riskConfig.label}</h3>
<p style="margin: 0 0 12px 0; font-size: 0.95em; line-height: 1.5;">
${riskConfig.message}
</p>
<button onclick="document.getElementById('dima-suspicious-details-modal')?.remove(); document.querySelector('#dima-modal .suspicious-details-btn').click()"
class="suspicious-details-btn"
style="background: white; color: ${riskConfig.color}; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s;">
Voir les détails du rapport
</button>
</div>
</div>
</div>
`;
}
modal.innerHTML = `
<div style="background: white; padding: 30px; border-radius: 20px; max-width: 900px; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 25px 50px rgba(0,0,0,0.3); animation: slideIn 0.3s ease-out;">
@ -257,6 +606,8 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</p>
</div>
${suspiciousAlert}
<!-- Métriques principales -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 15px; margin-bottom: 25px;">
<div style="background: linear-gradient(135deg, ${this.analysisResults.riskColor}, ${this.adjustColor(this.analysisResults.riskColor, -15)}); color: white; padding: 20px; border-radius: 12px; text-align: center; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
@ -277,17 +628,7 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</div>
</div>
<!-- Résumé exécutif -->
${this.analysisResults.globalScore > 15 ? `
<div style="background: ${this.analysisResults.globalScore >= 50 ? '#ffebee' : this.analysisResults.globalScore >= 30 ? '#fff3e0' : '#e8f5e8'}; padding: 20px; border-radius: 12px; margin-bottom: 25px; border-left: 4px solid ${this.analysisResults.riskColor};">
<h4 style="margin: 0 0 10px 0; color: ${this.analysisResults.riskColor}; font-size: 1.1em;">
${this.getScoreIcon(this.analysisResults.globalScore)} Résumé de l'analyse
</h4>
<p style="margin: 0; color: #444; line-height: 1.5; font-size: 0.95em;">
${this.generateExecutiveSummary()}
</p>
</div>
` : ''}
${this.generatePhaseAnalysis()}
<!-- Informations sur la page -->
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
@ -300,38 +641,6 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</div>
</div>
<!-- Répartition par phase DIMA -->
${this.analysisResults.phaseScores && Object.keys(this.analysisResults.phaseScores).length > 0 ? `
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
<h4 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 1.1em;">📊 Répartition par phase DIMA</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 15px;">
${Object.entries(this.analysisResults.phaseScores).map(([phase, score]) => {
const maxScore = Math.max(...Object.values(this.analysisResults.phaseScores));
const percentage = maxScore > 0 ? (score / maxScore) * 100 : 0;
return `
<div style="text-align: center;">
<div style="font-size: 0.85em; color: #666; margin-bottom: 8px; font-weight: 500;">
${this.getPhaseEmoji(phase)} ${phase}
</div>
<div style="background: ${this.getPhaseColor(phase)}; height: 12px; border-radius: 6px; margin-bottom: 8px; overflow: hidden;">
<div style="background: ${this.analysisResults.riskColor}; height: 100%; width: ${Math.min(percentage, 100)}%; border-radius: 6px; transition: width 0.8s ease;"></div>
</div>
<div style="font-size: 0.8em; font-weight: bold; color: #333;">
${score.toFixed(1)} pts
</div>
<div style="font-size: 0.7em; color: #888;">
${this.getPhaseDescription(phase)}
</div>
</div>
`;
}).join('')}
</div>
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; font-size: 0.8em; color: #666; text-align: center;">
💡 La matrice DIMA analyse comment l'information traverse les 4 phases cognitives
</div>
</div>
` : ''}
<!-- Message si aucune technique -->
${this.analysisResults.detectedTechniques.length === 0 ? `
<div style="background: linear-gradient(135deg, #d4edda, #c3e6cb); color: #155724; padding: 25px; border-radius: 12px; text-align: center; border: 1px solid #c3e6cb;">
@ -370,7 +679,7 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
${technique.matchedKeywords?.length > 0 ? `
<div style="margin-top: 10px;">
<div style="font-size: 0.85em; color: #666; margin-bottom: 6px; font-weight: 500;">
🔍 Mots-clés détectés:
🔎 Mots-clés détectés:
</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
${technique.matchedKeywords.slice(0, 4).map(keyword =>
@ -414,6 +723,10 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
#dima-modal button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
@ -425,6 +738,11 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// Ajouter l'événement pour le bouton des détails du site suspect
modal.querySelector('.suspicious-details-btn')?.addEventListener('click', () => {
this.showSuspiciousSiteDetails();
});
document.body.appendChild(modal);
this.log('Modal affiché');
@ -434,5 +752,6 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
}
}
}
// Make UIManager available globally for Chrome extension
window.UIManager = UIManager;
window.UIManager = UIManager;