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 @@ +