Add killfeed module
This commit is contained in:
BIN
modules/killfeed/assets/genjikill.png
Normal file
BIN
modules/killfeed/assets/genjikill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
138
modules/killfeed/css/killfeed.css
Normal file
138
modules/killfeed/css/killfeed.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
216
modules/killfeed/css/killfeed.less
Normal file
216
modules/killfeed/css/killfeed.less
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
138
modules/killfeed/killfeed.js
Normal file
138
modules/killfeed/killfeed.js
Normal file
@@ -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();
|
||||||
|
})();
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
<!-- Modules -->
|
<!-- Modules -->
|
||||||
<script type="text/javascript" src="modules/popup/popup.js"></script>
|
<script type="text/javascript" src="modules/popup/popup.js"></script>
|
||||||
<script type="text/javascript" src="modules/chat/chat.js"></script>
|
<script type="text/javascript" src="modules/chat/chat.js"></script>
|
||||||
|
<script type="text/javascript" src="modules/killfeed/killfeed.js"></script>
|
||||||
<script type="text/javascript" src="modules/scrolling-banner/scrolling-banner.js"></script>
|
<script type="text/javascript" src="modules/scrolling-banner/scrolling-banner.js"></script>
|
||||||
<script type="text/javascript" src="modules/alert-banner/alert-banner.js"></script>
|
<script type="text/javascript" src="modules/alert-banner/alert-banner.js"></script>
|
||||||
<script type="text/javascript" src="modules/big-emote/big-emote.js"></script>
|
<script type="text/javascript" src="modules/big-emote/big-emote.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user