Text Not Rendering in Image Generation on Vercel (Next.js 15)

I’m working on a Next.js 15 project where I generate images dynamically. The implementation works fine on localhost, but after deploying to Vercel, the generated image contains blank boxes instead of text.
Even when using a system font like “Arial”, the issue persists.

Here is the relevant code snippet:

'use server'
    import sharp from 'sharp'
    import path from 'path'
    
    interface CreateArgs {
      nomineName: string
      awardName: string
      regionName: string
    }
    
    // Estimate font size so text fits in 1 line and does not exceed SVG width
    function getNomineFontSize(name: string, maxFont = 48, minFont = 16, maxWidth = 380) {
      // Average character width factor (adjust for your font)
      const avgCharWidth = 0.6 // 0.6em per character is a good estimate for most sans-serif fonts
      const estimatedFontSize = Math.floor(maxWidth / (name.length * avgCharWidth))
      return Math.max(minFont, Math.min(maxFont, estimatedFontSize))
    }
    
    // Helper to wrap text at a max character length per line, maxLines = 2
    function wrapText(text: string, maxChars = 20, maxLines = 2) {
      const words = text.split(' ')
      const lines: string[] = []
      let currentLine = ''
    
      for (const word of words) {
        if ((currentLine + ' ' + word).trim().length > maxChars) {
          lines.push(currentLine.trim())
          currentLine = word
          if (lines.length === maxLines - 1) {
            // Add the rest of the words to the last line and break
            currentLine += ' ' + words.slice(words.indexOf(word) + 1).join(' ')
            break
          }
        } else {
          currentLine += ' ' + word
        }
      }
      if (currentLine) lines.push(currentLine.trim())
      // Ensure no more than maxLines
      return lines.slice(0, maxLines)
    }
    
    // Estimate font size for award lines so each line fits in maxWidth (e.g. 390px)
    function getAwardFontSize(lines: string[], maxFont = 28, minFont = 14, maxWidth = 390) {
      // Find the longest line
      const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), '')
      const avgCharWidth = 0.6
      const estimatedFontSize = Math.floor(maxWidth / (longest.length * avgCharWidth))
      return Math.max(minFont, Math.min(maxFont, estimatedFontSize))
    }
    
    export const createBadge = async ({
      nomineName,
      awardName,
      regionName
    }: CreateArgs) => {
      try {
        const rootPath = process.cwd()
        const imagePath = path.resolve(rootPath, './public/badge/badge.png') // Path to your image
        const image = sharp(imagePath)
    
        // Nominee name font size (1 line, full width)
        const nomineFontSize = getNomineFontSize(nomineName)
    
        // Award name: wrap to 2 lines, font size so each line fits maxWidth
        const awardLines = wrapText(awardName, 20, 2)
        const awardFontSize = getAwardFontSize(awardLines)
        const awardTspans = awardLines
          .map((line, i) => `<tspan x="50%" dy="${i === 0 ? 0 : 28}">${line}</tspan>`)
          .join('')
    
        const svgText = `
            <svg width="440" height="440">
              <style>
                .nomine { fill: #ba8f30; font-size: ${nomineFontSize}px; font-weight: bold; font-family: "Arial", sans-serif; }
                .region { fill: #ba8f30; font-size: 40px; font-weight: bold; font-family: "Arial", sans-serif; }
                .award { fill: white; font-size: ${awardFontSize}px; font-weight: bold; font-family: "Arial", sans-serif; }
              </style>
              <text 
                x="50%" 
                y="200px" 
                alignment-baseline="middle" 
                text-anchor="middle" 
                class="nomine"
              >${nomineName}</text>
              <text x="50%" y="255px" alignment-baseline="middle" text-anchor="middle" class="region">${regionName}</text>
              <text x="50%" y="380px" alignment-baseline="middle" text-anchor="middle" class="award">
                ${awardTspans}
              </text>
            </svg>
          `
    
        const buffer = await image
          .composite([{ input: Buffer.from(svgText), top: 30, left: 30 }])
          .toBuffer()
    
       
        return { buffer}
      } catch (error) {
        console.error('Error generating badge:', error)
        throw error // Rethrow the error to be handled by the caller
      }
    }

Troubleshooting Attempts:

  • Works correctly on localhost
  • Tried using system fonts (e.g., Arial)
  • Issue persists only on Vercel deployment
    I’ve attached images from both localhost and Vercel to show the difference.

localhost generated image
localhost generated image

vercel generated image
vercel generated image

Has anyone faced a similar issue or found a solution? Any insights would be appreciated!
Thanks in advance.

Why typescript is narrowing a type even after it has changed [double]

I have a simple game with an status that tells me if the game is going or not. Like so type GameStatus = “running” | “won”; If in my main function I check if the status is “won” then return, then TypeScript properly infert that after, the status can only be “running”. But if I call a function changes the status, TypeScript still thinks the status is only “running” even after it has changed. Why is that?

Example (this is an over simplification to show case the issue)

Even if I change the game.play(move) for a more straighforward game.win() where game.win() only sets the status to “won” without any conditions, TypeScript still thinks that status is “running” even when it should infer it to be “won”. Only if I change the status in the body of the function, and not by calling a function, TypeScript recognizes that the value of status has changed. Why is this behavior? is there some configuration that can be changed or maybe a different way to declare this so that TypeScript can infer the type properly?

How to pass parameters in embedded Power BI paginated report?

I’m using the Power BI Angular library to embed a paginated report with no problems, as defined in https://github.com/microsoft/PowerBI-client-angular

        <powerbi-paginated-report
            [embedConfig]="embedConfig"
            [cssClassName]="'container'">
        </powerbi-paginated-report>

Problem happens when I add parameters to the paginated report. I defined Internal parameters without defaults, where the intent is to pass these parameters when the report is embedded/rendered.

I tried two methods, unsuccessfully:

  1. Append to the Embed URL string the parameter name and values:

    embedUrl = embedUrl + '&rp:fromsk=20&rp:area=A' 
    
  2. Define the parameters in the embedded config object:

        this.embedConfig = {
             type: 'report',
             accessToken: '**********',
             tokenType: models.TokenType.Embed,
             permissions: models.Permissions.ReadWrite,
             viewMode: models.ViewMode.View,
             settings: {
                 parameterValues: [
                     { name: "area", value: "ab" },
                     { name: "fromsk", value: 20 }
                 ]
             }
         };
    

The problem is that either the parameters are ignored or the page is blank. What method should I use? How to make this work?

javafx js invoke java method success, but return failed

I try to invoke a java method in js method, and the java method worked but can’t get the return value, Moreover, the subsequent js cannot be executed either. Unfortunately, I can’t obtain the error log of js

onMounted(() => {
            alert("ready");
            // console.info has been redirect to java log and work fine
            console.info(window.bridge.getLocalApps());
        });

return bad:

    public String getLocalApps() {
        ArrayList<LocalAppRes> localAppRes = new ArrayList<>();
        LocalAppRes localApp = new LocalAppRes();
        localApp.name = "test1";
        localApp.version = "version1";
        localApp.author = "author1";
        localAppRes.add(localApp);
        String ret = JSONObject.toJSONString(localAppRes);
        log.info("getLocalApps: {}", ret); // log ok...
        return ret;
    }

return ok:

    public String getLocalApps() {
        return "[{"author":"author1","name":"test1","version":"version1"}]";
    }

My project(maven) used jdk is corretto-21.0.6.
JSON lib is fastjson2.

I don’t know the specific implementation logic of invoke Java from JS in JavaFX, and want to know how to handle it.

Hope and thanks for every reply~~~~

How do I update `grid-template-columns` in inline styles using Python and Selenium?

I’m using Python and Selenium to dynamically change the inline CSS of a `<div>` element. Updating `background-color` works fine, but I can’t seem to change `grid-template-columns`.

from selenium import webdriver

from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get(“http://localhost/test.html”) # Simple HTML file with a grid div

grid = driver.find_element(By.ID, “grid”)

driver.execute_script(“arguments[0].style.backgroundColor = ‘yellow’;”, grid) # Works

driver.execute_script(“arguments[0].style.gridTemplateColumns = ‘auto auto auto’;”, grid)

How can I create a fully type-safe useForm hook in React with dynamic field names in TypeScript?

I’m building a custom useForm() hook in React with TypeScript that handles form state generically.
I want to:

Infer the shape of the form values from the generic type T

Support dynamic field updates like setValue(’email’, ‘[email protected]’) with autocompletion and type safety.

I’ve tried using Record<keyof T, any>, but I’m struggling to ensure both flexibility and strong typing for dynamic keys.
What’s the recommended way to type the state and setValue function for this kind of hook?

How do I change the innerHTML of a newly created element watched by a Mutual Observer, WITHOUT causing memory leaks and freezes?

Imagine a website that has infinite scrolling, where a link saying download, appears underneath every post. I would like to change from a word to something else of anything else. (In my case a 24×24 icon image rather than the word “download” but don’t worry about that for now).

HTML

<div id="container">
<span class="download"> Initial download link</span>
</div>

CSS

body { background-color: #1c1c1c; }

span{ 
background-color: darkcyan; color: #dcdcdc; 
padding: 8px; font-family: Tahoma; 
/* font-weight: bold; */
display: inline-block;
margin-right: 8px; margin-bottom: 8px;
border: 2px ridge #1c1c1c;
}

.download {
    background-color: #cc3c3c;
    border: 3px ridge #3c3c3c;
}

Javascript

let i = 1
function insertElement() {
  var newSpan = document.createElement("span")
  newSpan.classList.add("download")
  var newText = document.createTextNode("new button #" + i)
  newSpan.appendChild(newText)
  //console.log(newSpan.classList)

  if (i < 210) {
    document.getElementById("container").appendChild(newSpan)
  }
  setTimeout(insertElement, 4200)
  i++
}

//(function(){
// do some stuff
setTimeout(insertElement)
//})();

var elemToObserve = document.getElementById("container")

//var observer = new MutationObserver(function (mutations) {
var observer = new MutationObserver((mutations) => {
  for (let mutation of mutations) {
    if (mutation.type === "childList") {
      //do NOT use console.log for mutation.target as it causes a HUGE INSTANTANEOUS memory leak
      //console.log(mutation)
      //if (mutation.nodeType === Node.ELEMENT_NODE ) {
      if (mutation.target.classList.contains("download") !== true) {
        console.log("a download button was found")
      }
    }
  }
})

observer.observe(elemToObserve, {
  childList: true,
  attributes: true,
  characterData: false,
  subtree: true,
})

Once this new element with a class of .download has been tracked using the MutationObserver, I would like to change the innerText or innerHTML of it.

You can see what’s going on, can’t you? When I use innerHTML or innerText to change the value of a newly tracked element from MutualObserver, it causes some recursion, to monitor a change of a change of a change.

Here are some ideas for what to change the textNode to. / or a base64 encoded image.

You can test out my work to try for yourself on jsfiddle.

How can I initially hide the active dropdown-menu when a vertical Bootstrap 5 menu is collapsed?

I have converted a horizontal Bootstrap 5 navbar into a vertical sidebar.

The sidebar, when collapsed, is less then 100px wide and shows only icons.

.page {
  min-height: 100vh;
}

.sidebar {
  box-shadow: 3px 0px 11px 0px rgba(0, 0, 0, 0.04);
  width: 82px;
  height: auto !important;
}

.sidebar.show {
  width: 200px;
}

.site-header {
  display: flex;
  align-items: center;
  padding: 0 10px;
  height: 60px;
  box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.05);
}

/* Logo Begin */
.logo-container {
  min-width: 82px;
  display: flex;
  align-items: center;
}

.brand {
  height: 60px;
  display: flex;
  padding-left: 5px;
  align-items: center;
  text-decoration: none;
}

.brand img {
  display: none;
  height: 30px;
  width: auto;
}
/* Logo End */

/* Menu Begin */
.sidebar .brand img.collapsed {
  display: inline;
}

.sidebar.show .brand img {
  display: inline;
}

.sidebar.show .brand img.collapsed {
  display: none;
}

.sidebar .dropdown-toggle {
    position: relative !important;
}

.sidebar .dropdown-toggle::after {
  display: none;
  position: absolute;
  top: 17px;
  right: 15px;
  content: "";
  border: 0;
  border-bottom: 1px solid #495057;
  border-right: 1px solid #495057;
  height: 6px;
  width: 6px;
  transform: rotate(45deg);
}

.sidebar .dropdown-toggle.show::after {
  transform: rotate(225deg);
}

.sidebar .navbar-nav .nav-link {
  position: relative;
  text-align: center;
  padding: 6px 12px;
  color: #198754 !important;
}

.sidebar.show .navbar-nav .nav-link {
  text-align: left;
}

.sidebar .navbar-nav .nav-link:hover,
.sidebar .navbar-nav .nav-link.active {
  background: #e9ecef;
}

.sidebar .navbar-nav .nav-link .link-text {
  display: none;
}

.sidebar.show .navbar-nav .nav-link .menu-icon {
  padding-right: 5px;
}

.sidebar.show .navbar-nav .nav-link .link-text {
  display: inline;
}

.sidebar.show .dropdown-toggle::after {
  display: inline;
}

.sidebar .dropdown-menu {
  border: none !important;
  position: absolute;
  left: 50px !important;
  top: 10px !important;
  border: 1px solid rgba(0, 0, 0, 0.04) !important;
  box-shadow: 3px 0px 11px 0px rgba(0, 0, 0, 0.05);
}

.sidebar.show .dropdown-menu {
  position: static;
  width: 100%;
  left: auto;
  padding: 0;
  box-shadow: none;
}

.sidebar .dropdown-menu .dropdown-item {
  color: inherit;
}

.sidebar.show .dropdown-menu .dropdown-item {
  padding-left: 40px;
}

.sidebar .dropdown-menu .dropdown-item.active,
.sidebar .dropdown-menu .dropdown-item:hover {
  background: #e9ecef;
}

/* Menu End */

.main {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.content {
  flex: 1;
  display: flex;
  flex-direction: column;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

<div class="page d-flex">
  <aside class="sidebar d-block bg-white">
    <div b-2esi1xgawe="" class="logo-container px-1">
      <button class="navbar-toggler mx-1" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar"
          aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon">
            <img src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e">
          </span>
        </button>

      <a href="#" class="brand">
          <img src="https://i.sstatic.net/bmJ0DHwU.png" alt="Logo">
          <img src="https://i.sstatic.net/rKpG1nkZ.png" alt="Logo" class="collapsed">
        </a>
    </div>
    <div class="navbar-vertical">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link" title="Now playing" href="#"><i class="fa-solid fa-house menu-icon"></i> <span
                class="link-text">Now playing</span></a>
        </li>
        <li class="nav-item">
          <a class="nav-link " title="Top rated" href="#"><i class="fa-solid fa-trophy menu-icon"></i> <span
                class="link-text">Top rated</span></a>
        </li>

        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle active show" title="Genres" href="#" role="button" data-bs-toggle="dropdown"
              aria-expanded="false">
              <i class="fa-solid fa-list menu-icon"></i> <span class="link-text">Genres</span>
            </a>
          <ul class="dropdown-menu dropdown-menu-end show" aria-labelledby="Genres">
            <li>
              <a class="dropdown-item" href="#">Action</a>
            </li>
            <li>
              <a class="dropdown-item active" href="#">Adventure</a>
            </li>
            <li>
              <a class="dropdown-item" href="#">Science Fiction</a>
            </li>
          </ul>
        </li>
      </ul>
    </div>
  </aside>

  <div class="main">
    <header class="site-header bg-e">
      <h1 class="page-title">Lorem ipsum dolor sit amet</h1>
    </header>
    <div class="container content">
      <h2 class="mt-3">Lorem, ipsum dolor sit amet consectetur adipisicing elit</h2>
      <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Placeat nam nobis recusandae rem aut nesciunt
        officiis incidunt voluptatum dicta doloremque, tempora suscipit debitis corporis nihil, corrupti tempore
        aspernatur praesentium. Dolorum.</p>
    </div>
  </div>
</div>

I am facing a problem when I make a dropdown-menu active and the sidebar is collapsed: the dropdown-menu is displayed when the page loads and I want it collapsed and displayed only when the user interacts with the dropdown toggle.

Otherwise, when the menu is expanded, all is ok as it is now.

Questions

  1. Is there a way to keep the dropdown-menu collapsed when the sidebar is collapsed, unless the dropdown toggle is clicked?
  2. Alternatively, when the sidebar is collapsed, how can I make the dropdown-menu show only when the dropdown toggle is hovered?

Nut.js not found but its in the Node_modules folder

I’ve been trying to make a very simple app with nut.js but I just can’t get it to work. I installed nut from a .tgz I built from the monorepo and I listed the depends out just to make sure i installed it correctly and it is there:


[email protected] C:UsersMarkDocumentsCodeMouse App

dependencies:
@nut-tree/nut.js 4.2.0
```. I made a script for testing its very simple: `"test": "node main.js"` but every time i run it I got this big error:

Error: Cannot find module ‘@nut-tree/nut.js/’
Require stack:

  • C:UsersMarkDocumentsCodeMouse Appmain.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1405:15)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1061:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1066:22)
    at Module._load (node:internal/modules/cjs/loader:1215:37)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
    at Module.require (node:internal/modules/cjs/loader:1491:12)
    at require (node:internal/modules/helpers:135:16)
    at Object. (C:UsersMarkDocumentsCodeMouse Appmain.js:1:13)
    at Module._compile (node:internal/modules/cjs/loader:1734:14) {
    code: ‘MODULE_NOT_FOUND’,
    requireStack: [ ‘C:UsersMarkDocumentsCodeMouse Appmain.js’ ]
    }
. I think i get this error when node tries to run this: `const nut = require("@nut-tree/nut.js/");`.
 I have no clue why it's not working and I hope someone can help!

I tried out different paths when requiring nut.js and I was obviously expecting it to run without errors. 

How to recreate a rocket launch animation using only HTML, CSS, and JavaScript (no canvas)?

I want to recreate a rocket launch animation similar to the one in this game:
https://100hp.app/astronaut/onewin/?exitUrl=https%253A%252F%252F1wufjt.life%252Fcasino&language=en&b=demo
The animation shows a rocket launching, flying up, and then flying away.
I noticed they do this using only HTML, CSS, and JavaScript, without canvas or WebGL.
I’m not sure what techniques to use to recreate this kind of smooth animation using just those technologies.

I tried using CSS animations and JavaScript to move and transform HTML elements representing the rocket, but I’m struggling to get smooth and realistic movement like the original.
I expected to have the rocket launch and fly smoothly, but my attempts look choppy or don’t have the same effects.
I’d appreciate any example or advice on the best approach to achieve this.

Chrome Extension popup can’t read updated chrome.storage.local data from content script

I’m developing a Chrome Extension that analyzes the current tab’s URL using a content script (contentKeywords.js) and saves the number of suspicious keywords found to chrome.storage.local under keyWords. I would like to pass the number of key words found to popup.js (that will display the info to index.html, the pop-up menu displayed in the top right of a browser extension).

When the popup (popup.js) opens, it tries to read keyWords, but always gets the default value (e.g. 0) — even though I can see in the console that the content script does run and correctly sets the value (I can verify this by reading it manually in the DevTools console).

Debugging notes:

  1. If I hardcode a value using chrome.storage.local.set({debug: 5}) in
    the content script, the popup reads that just fine when adjusting
    the code to read ‘debug’.
  2. Adding setTimeout delays in popup.js (even up to 1000ms) doesn’t reliably solve the problem (see code).
  3. Content script is listed correctly in manifest.json and runs as expected (verified with console logs).
  4. Permissions include “storage” and content script matches “<all_urls>”.

Can anyone help me so that popup.js can read the correct value and pass it to index.html (the pop-up extension menu)

contentKeywords.js:

const susKeywords = ["warning","urgent","login","confirm"];



function checkURLForKeywords() {
  const currentURL = window.location.href.toLowerCase();  

  let noKeywords = 0;
  for (let keyword of susKeywords) {
    if (currentURL.includes(keyword.toLowerCase())) {
      console.log(`Suspicious keyword: "${keyword}" was found in the URL.`);
      noKeywords++;
    }
  }

  if (noKeywords === 0) {
    console.log("No suspicious keywords found in the URL.");
    chrome.storage.local.set({
    keyWords: 0
  });
  }

  else if (noKeywords === 1) {
    console.log("1 suspicious keyword found");
    chrome.storage.local.set({
    keyWords: 1
  });
  }

  else if  (noKeywords === 2) {
    console.log("2 suspicious keywords found");
    chrome.storage.local.set({
    keyWords: 2
  });
  }

  else if  (noKeywords >= 3) {
    console.log("3 or more suspicious keywords found!");
    chrome.storage.local.set({
    keyWords: 3
  });
  }
}
checkURLForKeywords();

Here is popup.js;

document.addEventListener('DOMContentLoaded', () => {
    setTimeout(() => {
    chrome.storage.local.get(['keyWords'], (data) => {
        const imagePhish = document.getElementById("phish-image")
        const phishtext = document.getElementById("phish-text")
        const phishTitle = document.getElementById("phish-title")

    if (imagePhish && phishtext && phishTitle) {
      const susCount = Number(data.keyWords?? 0);
      console.log("susWords value from storage:", susCount);

      phishtext.textContent = `Suspicious keyword count: ${susCount}`;
      phishTitle.textContent = `Phishing Score: ${susCount}`;

      if (susCount === 5) {
        imagePhish.src = "images/yes1.png";
      } else if (susCount === 2) {
        imagePhish.src = "images/maybe1.png";
      } else if (susCount === 3) {
        imagePhish.src = "images/no1.png";
      } else {
        imagePhish.src = "images/yes1.png"; // Assume safe
      }
    }
    
  });
  }, 1000);
});

Thanks in advance for any help… 🙂

How to decompile a V8 bytecode (.jsc) file created with Bytenode?

I’m working with an Electron application that uses Bytenode to compile JavaScript files into V8 bytecode (.jsc files). I need to analyze the code for security auditing purposes, but I can’t access the original source.

What I’ve tried:

  1. Looking for official decompilation tools for Bytenode, but couldn’t find any
  2. Using javascript-decompiler with jsd main.jsc, but it only produced incomplete/corrupted output
  3. Creating a wrapper script that loads the .jsc file and attempts to use reflection:
    const bytenode = require('bytenode');
    const module = require('./main.jsc');
    console.log(Object.keys(module));
    

    But this only reveals exported objects, not the implementation details

Environment details:

  • Node.js version: 16.15.0
  • Electron version: 21.3.1
  • Bytenode version: 1.4.1
  • File details: main.jsc (4.2MB)

Questions:

  1. Is there a reliable way to decompile .jsc files back to JavaScript?
  2. Are there any tools that can extract function signatures or API information from V8 bytecode?
  3. If full decompilation isn’t possible, are there techniques to understand the code flow or behavior without the source?

I understand that bytecode is designed to be difficult to reverse engineer, but any insights or partial solutions would be greatly appreciated.

Pop-up window after submitting

I have ACF form to send opinions. Everything works fine, the submit button sends opinon to the server. The problem is that when the user presses the submit button, a pop-up window appears saying: “Leave the page? The changes you made may not be saved.” I will mention that after submitting the form, the user actually goes to another subpage (thank you page), but this subpage is on the same domain. Is there any safe way to prevent this window from appearing and redirect user on “thank you page” and still have proper recapcha verification?

JavaScript

function onSubmit(token) {
        document.getElementById("acf_testi").submit();
}

PHP

<main>
        <div class="container testi">
                <div class="wrapp">
                        <div class="content">
                                <?php if( have_posts() ):
                                        while( have_posts() ): the_post();
                                                the_content();
                                        endwhile;
                                endif; ?>
                        </div>
                        <div class="testi-form">
                                <h2>Add opinion</h2>
                                <?php $settings = array(
                                        'post_id' => 'new_post',
                                        'post_title' => false,
                                        'post_content' => false,
                                        'id' => 'acf_testi',
                                        'new_post' => array(
                                                'post_type' => 'testimonials',
                                                'post_status' => 'pending',
                                        ),
                                        'submit_value' => __("Send opinion", 'acf'),
                                        'html_submit_button'  => '<input id="submit-testi" type="submit" class="acf-button button button-primary button-large g-recaptcha" data-sitekey="MY_SITE_KEY" data-callback="onSubmit" data-action="submit" value="%s" />',
                                        'return' => home_url('/thank-you-page/'),
                                );
                                acf_form( $settings ); ?>
                        </div>
                </div>
        </div>
</main>

function.php

/* reCaptcha ACF */
add_filter("acf/pre_save_post", function($post_id) {
    if (!isset($_POST["g-recaptcha-response"])) {
        wp_die("Error reCAPTCHA: Missing tokena.");
    }

    $secret = "MY_SECRET_KEY";
    $response = $_POST["g-recaptcha-response"];
    $remoteip = $_SERVER["REMOTE_ADDR"];

    $url = "https://www.google.com/recaptcha/api/siteverify";
    $data = [
        "secret"   => $secret,
        "response" => $response,
        "remoteip" => $remoteip
    ];

    $options = [
        "http" => [
            "header"  => "Content-type: application/x-www-form-urlencodedrn",
            "method"  => "POST",
            "content" => http_build_query($data)
        ]
    ];

    $context  = stream_context_create($options);
    $result = file_get_contents($url, false, $context);
    $resultJson = json_decode($result);

    if (!$resultJson->success || $resultJson->score < 0.5) {
        wp_die("Error reCAPTCHA: Verification failed. Please try again.");
    }

    return $post_id;
});

How to select a list of names and control the display of a div?

I have a list of names (ul, li elements) and a hidden div on top
When a name is clicked (selected), the background color of the name should change and the div on top display block. Only when all names are deselected, the div displays none.
I’ve tried something but finding challenge with the last part (get the div display none only none of the name is selected).

const usersListItems = document.querySelectorAll(".users__list-item");
const myDiv = document.querySelector(".mydiv");
usersListItems.forEach((usersListItem) => {
  usersListItem.addEventListener("click", () => {
    usersListItem.classList.toggle("selected");
    if (usersListItem.classList.contains("selected")) {
      myDiv.classList.remove("hide");
    } else {
      myDiv.classList.add("hide");
    }
  });
});
.mydiv {
  width: 100%;
  height: 50px;
  border: 1px solid;
}

.hide {
  display: none;
}

.users__list-item,
.no-users {
  list-style: none;
  padding: 0.5em;
  background: #ddd;
  margin: 0.5em;
}

.selected {
  background: dodgerblue;
  color: white;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>List Selection</title>
</head>

<body>
  <div class="">
    <div class="mydiv hide"></div>
    <ul class="users__list">
      <li class="no-users hide">No users</li>
      <li class="users__list-item">User 1</li>
      <li class="users__list-item">User 2</li>
      <li class="users__list-item">User 3</li>
      <li class="users__list-item">User 4</li>
      <li class="users__list-item">User 5</li>
      <li class="users__list-item">User 6</li>
      <li class="users__list-item">User 7</li>
    </ul>
  </div>
</body>

</html>