Foren
The great place to discuss topics with other users
🎉 [TUTORIAL] Visual Effects with Straw Hats, Flags, and Fireworks for Sngine (XEngine Theme Compatible)
'🎉 [TUTORIAL] Visual Effects with Straw Hats, Flags, and Fireworks for Sngine (XEngine Theme Compatible)
Hello everyone!
I'm a Brazilian developer, and in Brazil, the month of June is known for the Festa Junina — a traditional celebration of Saint John (São João) held across the country. It's a colorful and festive time filled with music, food, dancing, and rustic decorations like straw hats, flags, and fireworks.
To bring some of that fun into the digital world, I created this lightweight visual effects library to decorate your Sngine platform with:
-
🟦 Animated festive flags
-
🎇 Fireworks explosions
-
👒 Straw hats on user avatars
⚠️ Note: This library was designed specifically for the XEngine theme. If you're using a different theme, you may need to adjust some CSS selectors.
✨ What does the script do?
-
Adds colorful waving flags to the top of the screen
-
Displays fireworks every few seconds
-
Places a straw hat on:
-
.post-avatar(post avatars) -
.x_user_info.main_bg_half(sidebar avatar) -
.profile-avatar-wrapper(profile page avatar)
-
⚙️ How to implement it on Sngine
No need to edit system files. You can easily add it via the admin panel:
-
Go to AdminCP > Design
-
Scroll to the Footer Custom JavaScript field ✅ (recommended)
-
Open this link to access the library code:
👉 https://pastebin.com/raw/LjubtjLp -
Copy all the content of the link
-
Paste it directly into the Footer Custom JavaScript field
-
Save your changes
✅ Why use Footer Custom JavaScript?
Because it runs after all page elements are loaded, ensuring the effects apply correctly.
🛠️ Customization Tips
-
🎨 Change flag colors in the
const cores = [...]array -
⏱️ Adjust firework frequency in the
setInterval(...) -
👒 Replace the hat image by editing the
hatUrlvariable
Thanks for your help!
I ran into an issue where the logo appeared greyed out and wasn’t clickable in dark mode.
After some tweaking and optimizations, I managed to fix it.
This solution is tailored for the default Sngine theme.
Here are the improvements I made:
-
🎏 Flags: 20 colorful swaying flags (reduced count for performance)
-
🎇 Fireworks: Clean canvas with no black flash (
clearRect()used) -
🎩 Hats: Straw hats added to avatars only when visible
-
📉 Low-End Detection: Skips all animations on weak devices
-
🔄 Dynamic Content: Hats appear on new posts via
MutationObserver-
Clear function and variable declarations with JSDoc comments for clarity
-
Consistent indentation and spacing (2 spaces)
-
Removed redundant empty lines
-
Clear separation between style, flags, fireworks, hats, and initialization
-
Used
forEachwith arrow functions consistently -
Added
"use strict";for better error catching(() => {
"use strict";// Colors for flags and fireworks
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'];
const hatUrl = "https://i.imgur.com/tk27acn.png";// Detect low-end devices to skip heavy animations
const isLowEnd = navigator.hardwareConcurrency <= 2 || navigator.deviceMemory <= 2;// --- Insert styles ---
const style = document.createElement('style');
style.textContent = `
.logo-wrapper:before, .logo-wrapper:after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
}
.logo-wrapper .logo {
position: relative;
z-index: 1;
}
.logo-wrapper:before {
background-color: var(--body-bg);
opacity: 0.85;
}
.logo-wrapper:after {
backdrop-filter: blur(12px);
}
.flag {
width: 30px;
height: 40px;
clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%);
animation: sway 2s infinite alternate ease-in-out;
position: fixed;
top: 0;
z-index: 0;
}
@keyframes sway {
0% { transform: rotate(-5deg); }
100% { transform: rotate(5deg); }
}
.straw-hat, .straw-hat-sider, .straw-hat-big {
position: absolute !important;
z-index: 1 !important;
pointer-events: none !important;
background-size: contain !important;
background-repeat: no-repeat !important;
background-position: center !important;
}
.straw-hat {
height: 23px !important;
width: 40px !important;
top: -10px !important;
left: 14% !important;
transform: translateX(-50%) rotate(334deg) !important;
}
.straw-hat-sider {
height: 23px !important;
width: 40px !important;
top: 2px !important;
left: 8% !important;
transform: translateX(-50%) rotate(334deg) !important;
}
.straw-hat-big {
height: 71px !important;
width: 106px !important;
top: -24px !important;
left: 94% !important;
transform: translateX(-50%) rotate(53deg) !important;
}
`;
document.head.appendChild(style);// --- Flags Creation ---
function createFlags(quantity = 20) {
const step = window.innerWidth / quantity;for (let i = 0; i < quantity; i++) {
const flag = document.createElement('div');
flag.className = 'flag';
flag.style.left = `${step * i}px`;
flag.style.backgroundColor = colors[i % colors.length];
document.body.appendChild(flag);
}
}// --- Fireworks Canvas Setup ---
const canvas = document.createElement('canvas');
Object.assign(canvas.style, {
position: 'fixed',
top: '0',
left: '0',
pointerEvents: 'none',
zIndex: '9997'
});
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();// --- Fireworks Explosion ---
function explodeFireworks(x, y) {
const particles = Array.from({ length: 20 }, () => ({
x,
y,
radius: Math.random() * 3 + 2,
angle: Math.random() * 2 * Math.PI,
speed: Math.random() * 3 + 1,
alpha: 1,
color: colors[Math.floor(Math.random() * colors.length)]
}));function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);let active = false;
for (const p of particles) {
p.x += Math.cos(p.angle) * p.speed;
p.y += Math.sin(p.angle) * p.speed;
p.alpha -= 0.02;if (p.alpha > 0) {
active = true;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI);
ctx.fillStyle = p.color + Math.round(p.alpha * 255).toString(16).padStart(2, '0');
ctx.fill();
}
}if (active) {
requestAnimationFrame(animate);
}
}animate();
}// --- Hats Management ---
const hatsConfig = [
{ selector: '.post-avatar', className: 'straw-hat' },
{ selector: '.x_user_info.main_bg_half', className: 'straw-hat-sider' },
{ selector: '.profile-avatar-wrapper', className: 'straw-hat-big' }
];/**
* Adds a hat to elements matching selector inside container
* @param {string} selector
* @param {string} className
* @param {HTMLElement|Document} container
*/
function addHat(selector, className, container = document) {
container.querySelectorAll(selector).forEach(el => {
if (!el.querySelector(`.${className}`)) {
const img = document.createElement('img');
img.src = hatUrl;
img.className = className;
el.appendChild(img);
}
});
}/**
* Apply all hats from config inside container
* @param {HTMLElement|Document} container
*/
function applyAllHats(container = document) {
hatsConfig.forEach(({ selector, className }) => addHat(selector, className, container));
}/**
* Adds hats to avatars that are visible in viewport (on scroll)
*/
function addHatsOnScroll() {
hatsConfig.forEach(({ selector, className }) => {
document.querySelectorAll(selector).forEach(el => {
if (!el.querySelector(`.${className}`)) {
const rect = el.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
addHat(selector, className);
}
}
});
});
}// --- Initialize hats on page load ---
applyAllHats();// --- Watch for new posts to add hats dynamically ---
const postsContainer = document.querySelector('.js_posts_stream ul');
if (postsContainer) {
const observer = new MutationObserver(mutations => {
mutations.forEach(({ addedNodes }) => {
addedNodes.forEach(node => {
if (node.nodeType === 1) {
applyAllHats(node);
}
});
});
});
observer.observe(postsContainer, { childList: true, subtree: true });
} else {
console.warn('Posts container not found. Check the selector.');
}// --- Add hats on scroll for lazy-loaded or offscreen avatars ---
window.addEventListener('scroll', addHatsOnScroll);// --- Initialize visual effects on capable devices ---
if (!isLowEnd) {
createFlags();setInterval(() => {
explodeFireworks(
Math.random() * window.innerWidth,
Math.random() * window.innerHeight / 2
);
}, 7000);
} else {
console.info('Low-end device detected — visual effects disabled.');
}
})();
-
Dieser code ist richtig!
Go to the Admin Panel / Design and add this code to the "Custom JavaScript" field at the bottom of the Footer.♥
################################
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']; const style = document.createElement('style'); style.textContent = ` .logo-wrapper .logo { position: relative; z-index: 1; } .logo-wrapper:before { background-color: var(--body-bg); opacity: 0.85; } .logo-wrapper:after { backdrop-filter: blur(12px); } .flag { width: 30px; height: 40px; clip-path: polygon(0 0, 100% 0, 100% 70%, 50% 100%, 0 70%); animation: sway 2s infinite alternate ease-in-out; position: fixed; top: 0; z-index: 0; } @keyframes sway { 0% { transform: rotate(-5deg); } 100% { transform: rotate(5deg); } } .straw-hat, .straw-hat-sider, .straw-hat-big { position: absolute !important; z-index: 1 !important; pointer-events: none !important; background-size: contain !important; background-repeat: no-repeat !important; background-position: center !important; } .straw-hat { height: 23px !important; width: 40px !important; top: -10px !important; left: 14% !important; transform: translateX(-50%) rotate(334deg) !important; } .straw-hat-sider { height: 23px !important; width: 40px !important; top: 2px !important; left: 8% !important; transform: translateX(-50%) rotate(334deg) !important; } .straw-hat-big { height: 71px !important; width: 106px !important; top: -24px !important; left: 94% !important; transform: translateX(-50%) rotate(53deg) !important; } `; document.head.appendChild(style); function createFlags(quantity = 40) { const widthStep = window.innerWidth / quantity; for (let i = 0; i < quantity; i++) { const flag = document.createElement('div'); flag.className = 'flag'; flag.style.left = `${widthStep * i}px`; flag.style.backgroundColor = colors[i % colors.length]; document.body.appendChild(flag); } } const canvas = document.createElement('canvas'); Object.assign(canvas.style, { position: 'fixed', top: 0, left: 0, pointerEvents: 'none', zIndex: 9997 }); document.body.appendChild(canvas); const ctx = canvas.getContext('2d'); function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resizeCanvas(); window.addEventListener('resize', resizeCanvas); function explodeFireworks(x, y) { const particles = Array.from({ length: 25 }, () => ({ x, y, radius: Math.random() * 3 + 2, angle: Math.random() * 2 * Math.PI, speed: Math.random() * 4 + 1, alpha: 1, color: colors[Math.floor(Math.random() * colors.length)] })); const interval = setInterval(() => { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(p => { p.x += Math.cos(p.angle) * p.speed; p.y += Math.sin(p.angle) * p.speed; p.alpha -= 0.02; if (p.alpha > 0) { ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI); ctx.fillStyle = p.color + Math.round(p.alpha * 255).toString(16).padStart(2, '0'); ctx.fill(); } }); if (particles.every(p => p.alpha <= 0)) clearInterval(interval); }, 30); } function addHat(selector, className, container = document) { const hatUrl = "https://i.imgur.com/tk27acn.png"; container.querySelectorAll(selector).forEach(avatar => { if (!avatar.querySelector(`.${className}`)) { const hat = document.createElement('img'); hat.src = hatUrl; hat.className = className; avatar.appendChild(hat); } }); } createFlags(); setInterval(() => { explodeFireworks( Math.random() * window.innerWidth, Math.random() * (window.innerHeight / 2) ); }, 5000); const hatsConfig = [ { selector: '.post-avatar', className: 'straw-hat' }, { selector: '.x_user_info.main_bg_half', className: 'straw-hat-sider' }, { selector: '.profile-avatar-wrapper', className: 'straw-hat-big' } ]; hatsConfig.forEach(({ selector, className }) => addHat(selector, className)); const postsContainer = document.querySelector('.js_posts_stream ul'); if (postsContainer) { const observer = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === 1) { hatsConfig.forEach(({ selector, className }) => addHat(selector, className, node)); } } } }); observer.observe(postsContainer, { childList: true, subtree: true }); } else { console.warn('Posts container not found. Check selector!'); }
################################