Forum

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 Στέλνω απάντηση
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!'); }

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