Forums

The great place to discuss topics with other users

🎉 [TUTORIAL] Visual Effects with Straw Hats, Flags, and Fireworks for Sngine (XEngine Theme Compatible)

'
Join the Conversation Publier la réponse
Luidy Felix
Member
Joined: 2025-05-29 16:31:31
2025-06-03 12:04:03

🎉 [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:

  1. Go to AdminCP > Design

  2. Scroll to the Footer Custom JavaScript field ✅ (recommended)

  3. Open this link to access the library code:
    👉 https://pastebin.com/raw/LjubtjLp

  4. Copy all the content of the link

  5. Paste it directly into the Footer Custom JavaScript field

  6. 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 hatUrl variable

Jane Marcia
Admin
Joined: 2025-05-17 02:14:16
2025-06-03 13:38:30

For my people we have something called Festas Juninas, where we celebrate São João and this came just on time 😂

Edy Lee
Admin
Joined: 2024-11-24 00:57:42
2025-06-03 15:28:48

Party on  guys  lets go 

Jakov Sir
Member
Joined: 2025-03-11 07:26:21
2025-06-05 01:56:32

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 forEach with 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.');
        }
      })();

Luidy Felix
Member
Joined: 2025-05-29 16:31:31
2025-06-05 11:21:05

@Jakov Sir, thanks a lot for the improvements to the code — it's much cleaner and more efficient, really! I hadn't even thought about the issue of performance on mobile devices. This attention to detail made all the difference. đŸ‘đŸ”„

Mike Sieck
Member
Joined: 2025-12-28 00:13:09
2025-12-28 01:53:59

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!'); }

################################