diff --git a/modules/killfeed/assets/genjikill.png b/modules/killfeed/assets/genjikill.png
new file mode 100644
index 0000000..a7bffb6
Binary files /dev/null and b/modules/killfeed/assets/genjikill.png differ
diff --git a/modules/killfeed/css/killfeed.css b/modules/killfeed/css/killfeed.css
new file mode 100644
index 0000000..4d91c40
--- /dev/null
+++ b/modules/killfeed/css/killfeed.css
@@ -0,0 +1,138 @@
+#killfeed-container {
+ position: absolute;
+ top: 20px;
+ left: auto;
+ right: 20px;
+ width: 620px;
+ height: 400px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ overflow: hidden;
+ z-index: 250;
+ color: #f0f0f0;
+}
+#killfeed-container ul#killfeed-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: flex-start;
+ gap: 6px;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+#killfeed-container .killfeed-item {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ opacity: 1;
+ transition: opacity 0.5s ease-out, transform 0.4s ease;
+ transform: translateY(0);
+}
+#killfeed-container .killfeed-item.appearing {
+ transform: translateY(10px);
+ opacity: 0;
+ animation: kf-slidein 0.35s ease-out forwards;
+}
+#killfeed-container .killfeed-item .kf-left,
+#killfeed-container .killfeed-item .kf-center,
+#killfeed-container .killfeed-item .kf-right {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
+ padding: 0 8px;
+ gap: 6px;
+ height: 36px;
+}
+#killfeed-container .killfeed-item .kf-left {
+ background: #66c4ff;
+ color: #0b2a3a;
+}
+#killfeed-container .killfeed-item .kf-left .kf-name-left {
+ font-size: 0.95rem;
+ font-weight: 700;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+#killfeed-container .killfeed-item .kf-left .kf-avatar-left {
+ width: 36px;
+ height: 36px;
+ object-fit: cover;
+ border-radius: 6px;
+ background: rgba(0, 0, 0, 0.2);
+ border: 2px solid rgba(255, 255, 255, 0);
+ box-sizing: border-box;
+ display: block;
+}
+#killfeed-container .killfeed-item .kf-center {
+ background: transparent;
+ box-shadow: none;
+ padding: 0 2px;
+}
+#killfeed-container .killfeed-item .kf-center .kf-event {
+ display: block;
+ height: 36px;
+ max-width: 120px;
+ object-fit: contain;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));
+}
+#killfeed-container .killfeed-item .kf-right {
+ background: #e44848;
+ color: #2a0b0b;
+}
+#killfeed-container .killfeed-item .kf-right .kf-avatar-right {
+ width: 36px;
+ height: 36px;
+ object-fit: cover;
+ border-radius: 6px;
+ background: rgba(0, 0, 0, 0.2);
+ border: 2px solid rgba(255, 255, 255, 0);
+ box-sizing: border-box;
+ display: block;
+}
+#killfeed-container .killfeed-item .kf-right .kf-name-right {
+ font-size: 0.95rem;
+ font-weight: 700;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+#killfeed-container.outline-mode .killfeed-item .kf-left,
+#killfeed-container.outline-mode .killfeed-item .kf-right {
+ background: transparent;
+ box-shadow: none;
+ padding: 0 4px;
+}
+#killfeed-container.outline-mode .killfeed-item .kf-left .kf-avatar-left {
+ background: rgba(0, 0, 0, 0.2);
+ border: 2px solid rgba(255, 255, 255, 0);
+}
+#killfeed-container.outline-mode .killfeed-item .kf-right .kf-avatar-right {
+ background: rgba(0, 0, 0, 0.2);
+ border: 2px solid rgba(255, 255, 255, 0);
+}
+#killfeed-container.outline-mode .killfeed-item .kf-left .kf-name-left,
+#killfeed-container.outline-mode .killfeed-item .kf-right .kf-name-right {
+ color: #f0f0f0;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
+}
+@keyframes kf-slidein {
+ from {
+ transform: translateY(10px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/modules/killfeed/css/killfeed.less b/modules/killfeed/css/killfeed.less
new file mode 100644
index 0000000..ca51815
--- /dev/null
+++ b/modules/killfeed/css/killfeed.less
@@ -0,0 +1,216 @@
+// out: killfeed.css, sourcemap: false, compress: false
+
+////////////////////////////////////
+// ⚙️ VARIABLES GLOBALES DE STYLE //
+////////////////////////////////////
+
+// --- Position et taille du module ---
+@pos-top: 20px;
+@pos-left: auto; // mettre à "20px" pour ancrer à gauche
+@pos-right: 20px; // mettre à "auto" pour ancrer à gauche
+@feed-width: 620px;
+@feed-height: 400px;
+
+// --- Couleurs Joueur 1 ---
+@p1-bg: #66c4ff; // fond du bloc joueur 1
+@p1-avatar-bg: rgba(0,0,0,0.2); // fond derrière la PP joueur 1
+@p1-avatar-border: 2px solid rgba(255,255,255,0); // bordure de la PP joueur 1
+
+// --- Couleurs Joueur 2 ---
+@p2-bg: #e44848; // fond du bloc joueur 2
+@p2-avatar-bg: rgba(0,0,0,0.2); // fond derrière la PP joueur 2
+@p2-avatar-border: 2px solid rgba(255,255,255,0); // bordure de la PP joueur 2
+
+// --- Style global ---
+@text: #f0f0f0;
+@shadow: rgba(0,0,0,0.35);
+@radius: 8px;
+@gapX: 10px;
+@gapY: 6px;
+@avatarSize: 36px;
+@fontName: 0.95rem;
+
+
+//////////////////////////////
+// 💬 CONTAINER PRINCIPAL //
+//////////////////////////////
+
+#killfeed-container {
+ position: absolute;
+ top: @pos-top;
+ left: @pos-left;
+ right: @pos-right;
+ width: @feed-width;
+ height: @feed-height;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end; // ✅ aligné à droite
+ overflow: hidden;
+ z-index: 250;
+ color: @text;
+
+ ul#killfeed-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ justify-content: flex-start;
+ gap: @gapY;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ //////////////////////////////
+ // 🧩 ÉLÉMENT DU KILLFEED //
+ //////////////////////////////
+ .killfeed-item {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: @gapX;
+ opacity: 1;
+ transition: opacity 0.5s ease-out, transform 0.4s ease;
+ transform: translateY(0);
+
+ // animation d'apparition
+ &.appearing {
+ transform: translateY(10px);
+ opacity: 0;
+ animation: kf-slidein 0.35s ease-out forwards;
+ }
+
+ // Trois zones : joueur 1 / event / joueur 2
+ .kf-left,
+ .kf-center,
+ .kf-right {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: @radius;
+ box-shadow: 0 2px 6px @shadow;
+ padding: 0 8px;
+ gap: 6px;
+ height: @avatarSize;
+ }
+
+ ///////////////////////////
+ // 🎯 JOUEUR 1 (à gauche)
+ ///////////////////////////
+ .kf-left {
+ background: @p1-bg;
+ color: #0b2a3a;
+
+ .kf-name-left {
+ font-size: @fontName;
+ font-weight: 700;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .kf-avatar-left {
+ width: @avatarSize;
+ height: @avatarSize;
+ object-fit: cover;
+ border-radius: 6px;
+ background: @p1-avatar-bg;
+ border: @p1-avatar-border;
+ box-sizing: border-box; // ✅ empêche le débordement vertical
+ display: block; // ✅ supprime l’espace baseline
+ }
+ }
+
+ ///////////////////////////
+ // ⚡ EVENT CENTRAL
+ ///////////////////////////
+ .kf-center {
+ background: transparent;
+ box-shadow: none;
+ padding: 0 2px;
+
+ .kf-event {
+ display: block;
+ height: @avatarSize;
+ max-width: 120px;
+ object-fit: contain;
+ filter: drop-shadow(0 2px 4px @shadow);
+ }
+ }
+
+ ///////////////////////////
+ // 🔥 JOUEUR 2 (à droite)
+ ///////////////////////////
+ .kf-right {
+ background: @p2-bg;
+ color: #2a0b0b;
+
+ .kf-avatar-right {
+ width: @avatarSize;
+ height: @avatarSize;
+ object-fit: cover;
+ border-radius: 6px;
+ background: @p2-avatar-bg;
+ border: @p2-avatar-border;
+ box-sizing: border-box; // ✅ idem : la bordure ne dépasse pas
+ display: block;
+ }
+
+ .kf-name-right {
+ font-size: @fontName;
+ font-weight: 700;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+
+ /////////////////////////////////////
+ // 🧱 MODE OUTLINE (sans fond bloc)
+ /////////////////////////////////////
+ &.outline-mode {
+ .killfeed-item {
+ .kf-left,
+ .kf-right {
+ background: transparent;
+ box-shadow: none;
+ padding: 0 4px;
+ }
+
+ .kf-left .kf-avatar-left {
+ background: @p1-avatar-bg;
+ border: @p1-avatar-border;
+ }
+
+ .kf-right .kf-avatar-right {
+ background: @p2-avatar-bg;
+ border: @p2-avatar-border;
+ }
+
+ .kf-left .kf-name-left,
+ .kf-right .kf-name-right {
+ color: @text;
+ text-shadow: 0 1px 2px @shadow;
+ }
+ }
+ }
+}
+
+//////////////////////////
+// ✨ ANIMATION ENTRÉE ✨
+//////////////////////////
+@keyframes kf-slidein {
+ from {
+ transform: translateY(10px);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/modules/killfeed/killfeed.js b/modules/killfeed/killfeed.js
new file mode 100644
index 0000000..c589800
--- /dev/null
+++ b/modules/killfeed/killfeed.js
@@ -0,0 +1,138 @@
+/* global loadCSSModule, SBdispatcher, getModuleUrl */
+;(function () {
+ const DEFAULT_DELAY_SECONDS = 12;
+ const MAX_ITEMS = 8;
+ const MODULE_ID = 'killfeed-container';
+
+ const moduleUrl = window.getModuleUrl ? window.getModuleUrl() : '';
+ loadCSSModule('overlay-killfeed-css', moduleUrl + '/css/killfeed.css');
+
+ // ==== Init DOM ====
+ function initModule() {
+ if (document.getElementById(MODULE_ID)) return;
+
+ const root = document.createElement('div');
+ root.id = MODULE_ID;
+
+ const list = document.createElement('ul');
+ list.id = 'killfeed-list';
+
+ root.appendChild(list);
+ document.body.appendChild(root);
+ }
+
+ // Construit un LI de killfeed
+ function buildKillItem(p1Name, p1Img, p2Name, p2Img, eventName) {
+ const li = document.createElement('li');
+ li.className = 'killfeed-item';
+
+ // Zone gauche (joueur 1)
+ const left = document.createElement('div');
+ left.className = 'kf-left';
+
+ const leftName = document.createElement('span');
+ leftName.className = 'kf-name kf-name-left';
+ leftName.textContent = p1Name || '';
+
+ const leftImg = document.createElement('img');
+ leftImg.className = 'kf-avatar kf-avatar-left';
+ if (p1Img) leftImg.src = p1Img;
+ leftImg.alt = p1Name ? `Avatar ${p1Name}` : 'Avatar joueur 1';
+
+ left.append(leftName, leftImg);
+
+ // Zone centrale (event)
+ const center = document.createElement('div');
+ center.className = 'kf-center';
+ const evtImg = document.createElement('img');
+ evtImg.className = 'kf-event';
+ if (eventName) evtImg.src = `${moduleUrl}/assets/${eventName}.png`;
+ evtImg.alt = eventName || 'Event';
+ center.appendChild(evtImg);
+
+ // Zone droite (joueur 2)
+ const right = document.createElement('div');
+ right.className = 'kf-right';
+
+ const rightImg = document.createElement('img');
+ rightImg.className = 'kf-avatar kf-avatar-right';
+ if (p2Img) rightImg.src = p2Img;
+ rightImg.alt = p2Name ? `Avatar ${p2Name}` : 'Avatar joueur 2';
+
+ const rightName = document.createElement('span');
+ rightName.className = 'kf-name kf-name-right';
+ rightName.textContent = p2Name || '';
+
+ right.append(rightImg, rightName);
+
+ li.append(left, center, right);
+ return li;
+ }
+
+ // Ajoute un event (kill)
+function addKill(p1Name, p1Img, p2Name, p2Img, eventName, delaySeconds) {
+ const list = document.getElementById('killfeed-list');
+ if (!list) return;
+
+ const li = buildKillItem(p1Name, p1Img, p2Name, p2Img, eventName);
+ li.classList.add('appearing');
+
+ // ✅ AJOUT EN DESSOUS (fin de liste)
+ list.appendChild(li);
+
+ // Auto-disparition
+ const ms = (isFinite(delaySeconds) && delaySeconds > 0)
+ ? delaySeconds * 1000
+ : DEFAULT_DELAY_SECONDS * 1000;
+
+ const fadeRemove = () => {
+ li.style.opacity = '0';
+ setTimeout(() => {
+ if (li.parentElement === list) list.removeChild(li);
+ }, 500);
+ };
+ setTimeout(fadeRemove, ms);
+
+ // Limite par nombre (supprime en haut)
+ while (list.children.length > MAX_ITEMS) {
+ list.removeChild(list.firstElementChild);
+ }
+
+ // Limite par hauteur (supprime en haut tant que ça dépasse)
+ const container = document.getElementById('killfeed-container');
+ if (container) {
+ while (list.scrollHeight > container.clientHeight && list.firstElementChild) {
+ list.removeChild(list.firstElementChild);
+ }
+ }
+
+ setTimeout(() => li.classList.remove('appearing'), 400);
+}
+
+
+
+ // Expose globalement
+ window.Killfeed = { addKill };
+
+ // === SBdispatcher Listener ===
+ // wsParam1 = pseudo joueur 1
+ // wsParam2 = image joueur 1
+ // wsParam3 = pseudo joueur 2
+ // wsParam4 = image joueur 2
+ // wsParam5 = eventName
+ // wsParam6 = délai (secondes)
+ if (window.SBdispatcher) {
+ SBdispatcher.on('killfeed-add', data => {
+ addKill(
+ data.param1, // Joueur 1
+ data.param2, // Avatar joueur 1
+ data.param3, // Joueur 2
+ data.param4, // Avatar joueur 2
+ data.param5, // Nom de l'event (image)
+ parseFloat(data.param6) // Délai (optionnel)
+ );
+ });
+ }
+
+ initModule();
+})();
diff --git a/overlay.html b/overlay.html
index 5330b5f..3271ba3 100644
--- a/overlay.html
+++ b/overlay.html
@@ -15,6 +15,7 @@
+