3.58 Breaking Change?

My mapping application broke with the upgrade to Google Maps JavaScript API v3.58 and above. I am trying to create a new google.maps.Polyline using a custom class that extends the Polyline with a few new methods I use frequently.

The error in the console is as follows:

TypeError: Class constructors cannot be invoked without 'new'
at new CustomPolyline.Polyline (webpack-internal:///...tomPolygon.js:31:32)

I create instances of my custom class using

  polylines.push(new CUST.CustomPolyline.Polyline(polygonType, this.mapInstance, polylineJSONObjects[i].coordinates, polylineJSONObjects[i].normalPolyAttributes, polylineJSONObjects[i].highlightPolyAttributes, polylineJSONObjects[i].normalPolyAttributes.mapLabelText));

The code executing is the constructor. I’m using a namespace called CUST and my extension is called CustomPolyline

'use strict';
(
  function (window, CUST, google) {

  if (typeof (CUST) === 'undefined') {
    CUST = {};
  }

  CUST.CustomPolyline.Polyline = function(polyType, googleMap, dvgCoordinatesString, 
                                       normalPolyAttributes, highlightPolyAttributes,
                                       labelText) {
  // Constructor
  if (this instanceof CustomPolyline.Polyline) {
    try {
      google.maps.Polyline.apply(this, arguments);
    } catch (ex) {
      // new Chrome update gave exception in 'apply' so used 'call'
      google.maps.Polyline.call(this, arguments);
    }

    this.base = google.maps.Polyline.prototype;

    <... constructor initialization code ...>
  }
 
  <... extension methods ...>

)(window, window.CUST, window.google);

If I switch to v3.57, this code works fine. I get the above error with any version newer than 3.57. I have reviewed all the release notes and see no indication of a breaking change in 3.58 that would cause this.

Could this be an ECMAScript version problem with the Google Maps API? If so, how can I fix it without rewriting my extension class completely (it’s thousands of lines of code)?

Why do querySelector and querySelectorAll NOT iterate within an iframe?

This works:

parent.document.getElementById('iframeID').contentWindow.document.querySelectorAll('*[id^="elementId"]');

But this doesn’t:

parent.document.getElementById('iframeID').querySelectorAll('*[id^="elementId"]');

According to querySelectorAll‘s reference:

The querySelector() method of the Element interface returns the first element that is a descendant of the element on which it is invoked that matches the specified group of selectors.

If contentWindow is a descendant of the iframe element, shouldn’t the iframe element should be recursively iterated until it, eventually, contentWindow and document are encountered, as it is the case with, for example:

div id="div1">
  <div id="div2">
    <div id="div3">
      <div id="div4">
        <div id="div5">

        </div>
      </div>
    </div>
  </div>
</div>


<script>
console.log(document.getElementById("div1").querySelectorAll('div'));
</script>

Using Bun + Hono, serveStatic middle throws 404 even though files to serve exist

I am trying to serve static frontend only react apps through bun and using hono for framework. My code for server is :

import { Hono } from "hono";
import { cors } from "hono/cors";
import { serveStatic } from "hono/bun";

const app = new Hono();

// Config
app.use(
  cors({
    origin: ["http://localhost", "https://localhost"],
    allowMethods: ["GET", "POST"],
    exposeHeaders: ["Content-Type"],
    maxAge: 300,
  })
);

// Static paths
app.use("/assets/*", serveStatic({ root: "../dist/links/assets/" }));
app.use(
  "/portfolio/assets/*",
  serveStatic({ root: "../dist/portfolio/assets/" })
);

// Routes
app.get("/", serveStatic({ path: "../dist/links/index.html" }));
app.get(
  "/portfolio",
  serveStatic({
    path: "../dist/portfolio/index.html",
  }),

);

// Start server
try {
  const server = Bun.serve({
    fetch: app.fetch,
    port: process.env.PORT || 3000,
  });
  console.log("Server started on port : ", server.port);
} catch (e) {
  console.error("Error occured while starting the server : ", e);
}

My directory structure is like :
enter image description here

I tried everything. Litterally every relative and absolute path possible. I used path.resolve(). It still wont serve the static files. Please help its frustrating. The docs are of no help at all.

Console.log result shows ${field} instead of the actual string

In the code below, I run:

node test-update.js

and I’m expecting output in the console such as:

. . .
Field: “name”
Data: “John Doe”
Data: “Jane Doe”
. . .

but instead I get:

. . .
Field: ${field}
Data: ${itm[field]}
Data: ${itm[field]}
. . .

I’ve tried

console.log(“Field: ” + field)
console.log(“Field, ” + field)
console.log('Field: ${field}')
console.log(“Field: ” + JSON.stringify(field))
console.log(“Field: ,” JSON.stringify(field))

Any ideas what I’m doing wrong? Thank you!

const data = [
    {
        id: 1,
        name: 'John Doe',
        address: '25 Lighting Way'
    },
    {
        id: 3,
        name: 'Jane Doe',
        address: '35 Pancake St.'
    },
    {
        id: 5,
        name: 'Jean Doe',
        address: '45 Magnolia Ave.'
    },
    {
        id: 6,
        name: 'Joe Doe',
        address: '55 Maple St.'
    }    
];

const makequery = () => {
    
    // Iterate over fields
    Object.keys(data[0]).filter((itm)=>itm!=="id").forEach((field) => {
        // Iterate over data
        // console.log("Field: ", JSON.stringify(field));
        console.dir(field);
        data.forEach((itm) => {
            // console.log("Data: ", JSON.stringify(itm[field]));
            console.dir(itm[field]);
        });

    })
};

makequery();

useInfiniteQuery fails on React Native with “observer.getCurrentResult is not a function” error in @tanstack/react-query ^5.66.3 or higher

When using useInfiniteQuery in React Native environments (Android and iOS), the app crashes with the error: “observer.getCurrentResult is not a function (it is undefined)”. This error does not occur in web environments. The hook works correctly in version 5.64.0 but fails in version 5.66.3.

Home.js:

const InfiniteQuery = useInfiniteQuery({
queryKey: 'QueryKey',
    queryFn: async ({pageParam = 0}) => {
        const response = await new Promise(resolve =>
            resolve({
                data: [],
                nextCursor: pageParam + 1,
            })
        );
        return {
            pages: [{data: response.data}],
            pageParams: [pageParam],
        };
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage) => {
        return lastPage.nextCursor || null
    },
});

App.tsx:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      staleTime: 5 * 60 * 1000,
      gcTime: 10 * 60 * 1000,
      suspense: false,
      useErrorBoundary: false,
    },
  },
});

<QueryClientProvider client={queryClient}>
  <Home />
</QueryClientProvider>

The issue appears to be specific to the React Native environment as it works fine on web. It seems the update from 5.64.0 to 5.66.3 introduced a regression that affects the native implementation of useInfiniteQuery.

Platform: React Native (Android and iOS)
React Query version: 5.66.3
Working version: 5.64.0
React Native version: 0.76.7
React version: 18.3.1

The useInfiniteQuery hook should function properly in React Native environments (Android and iOS) just as it does in version 5.64.0. It should successfully initialize and allow fetching paginated data without throwing the “observer.getCurrentResult is not a function” error. The API should be consistent across web and mobile platforms.

resetField({ value: undefined }) prevents the form from becoming valid after updating the field with Vee-Validate and Yup

I have a multi-step form using Vue3 and Vee-Validate with Yup. I want to reset a field to remove it from the values and then fill it again with a new value. But when I do this, the meta.valid tag is still “false” instead of becoming “true” as the form is now valid with a correct value.

If it helps :

https://stackblitz.com/edit/vee-validate-v4-multi-step-form-composition-8hwywbjf?file=src%2Fcomponents%2FFirstStep.vue

Thanks everyone 🙂

Loading a Javascript File as an ESModule in a Blazor Server App

I am trying to run a self hosted dice rolling app in a Blazor Server Application. The original site comes from here. https://fantasticdice.games/docs/intro. This site loads the file dice-box-min.js in a dice.js javascript file using import. I believe this means it loads dice-box-min.js into the object DiceBox according to the site as an ESModule.

This is not able to work in a Blazor Server App. Apparently Blazor’s JSInterop cannot handle ES6 import directly inside a dynamically loaded module. So I need an alternative. Here is what I have thus far.

This is currently a Blazor Server App .NET9 Application.

In my App.razor I have the following two lines in my tag.

<script src="js/dice.js"></script>
<script type="module">
    import DiceBox from './js/dice-box-min.js';
    window.DiceBox = DiceBox;
</script>

This is my dice.js file currently


let diceBox;  // Store a global reference

// Initialize DiceBox
async function initializeDiceBox() {
    try {
        const DiceBox = await waitForDiceBox();
        diceBox = DiceBox;

        if (diceBox != null && diceBox != undefined) {
            diceBox = new DiceBox("#dice-box", {
                assetPath: "/public/assets/DiceBox/",
                theme: "default",
                themeColor: "#feea03",
                offscreen: false,
                scale: 6
            });
            await diceBox.init();
        }
    } catch (error) {
        console.error(error);
    }
}

// Roll dice with a given dice notation (e.g., "4d6")
async function rollDice(diceNotation) { 
    if (diceBox != null && diceBox != undefined) {
        await initializeDiceBox();
    }
    await diceBox.roll(diceNotation);
}

function waitForDiceBox() {
    return new Promise((resolve, reject) => {
        let attempts = 0;
        const check = () => {
            if (window.DiceBox) {
                console.error("returning dice box!");
                resolve(window.DiceBox);
            } else if (attempts < 10) {
                attempts++;
                setTimeout(check, 200); // Retry every 200ms
            } else {
                reject("DiceBox failed to load.");
            }
        };
        check();
    });
}

window.initializeDiceBox = initializeDiceBox;
window.rollDice = rollDice;

/public/assets/DiceBox/ contains libraries and assets for this application to run as well. This is loading correctly.

WaitForDiceBox is also returning “returning dice box” letting me know that the import I believe worked.

In my blazor razor file I have an onevent for a button. It is executing this line.

await JSRuntime.InvokeVoidAsync("rollDice", "4D6");

When I run my application I get this in my console screen in the browser.

Error Screen

Now I don’t know why I get the “old api” error. When I create DiceBox it is a single argument of configuration statements. I’m pretty sure this might not be the cause but if it is I’m still at a loss to what it actually wants.

I’m more worried about the idea that it can’t load world.onscreen.min.js

Inside of dice-box-min.js there is an import call to this js file. I’m wondering if this has the exact same issue as dice-box-min.js had where loading ESModules dynamically was the problem. If so I have no idea how to address or fix this.

Is it possible that I have to “retool” dice-box-min.js to remove that import? Maybe also download it and include it in app.razor? Is there another issue here that I may have overlooked. I’m very new to Blazor Server Apps / Javascript ESModules and the like.

I also have a github repository of the issue in a barebones application if you would like to see the setup entirely.

https://github.com/davidklecker/FantasticDiceBlazorServerApp.git

What I expect to see are four 6 sided dice roll across the screen.

Thank you for any help you can.

Min. height for stacked bar chartjs

I’ve a vertically stacked bar chart in chart.js. Since the bars are being generated based on some userinput, it’s possible that I get a chart that would look something like this:

Example

I want to enforce a minimum height for each stack to ensure that even small values are displayed more clearly.

In my current approach I’m checking if the height of a stack is below a certain value and then increase it’s height with getProps(). The problem here is that even though the height is correctly set, the corresponding stack isn’t being updated.

{
    id: 'ExtraHeightPlugin',
    afterDatasetsDraw: (chart: Chart <ChartType>) => {
        chart.data.labels.forEach((label, index) => {
            chart.data.datasets.forEach((dataset, datasetIndex) => {
                let currentStackHeight: number = chart.getDatasetMeta(datasetIndex).data[index].getProps(['height'])['height'];
                if (currentStackHeight> 0 && currentStackHeight< 35) {
                    currentStackHeight+= 35;
                    chart.getDatasetMeta(datasetIndex).data[index].getProps(['height'])['height'] = currentStackHeight;
                }
            });
        });
    }
}

Any help would be greatly appreciated.

Button click event not fired

In the setup form of an ESP32 device the Update button works in one card only.
For the setup page jquery, ajax, bootstrap and arduino jsonformer libraries are used.
The dialog is mainly generated by jsonFormer.jquery.js
The dialog comprises 3 cards.

Setup dialog
.

you see the 3 buttons on the bottom.
To modify the configuration I need to select one of the device tabs e.g. safetymonitor-0
There I can change a parameter , click on UPDATE to send a json coded information to the ESP32 . When clicking on SAVE the modified dota is written to the setup.html which in the internal memory of the ESP32. ( Looks a bit strange to me but so it was in the repo.)
This works with safetymonitor-0.
It does not work with obervationcondition-0.
To get a feedback on the click event I added the alert instruction.
The REFRESH and the SAVE buttons are working.
The UPDATE button works only if I do not change any parameter in the form.
Here the input area:

Top section of the form

Here the buttons to click.

Button area
After changing at least one of the paramters the UPDATE button does not work.

Here the html code:

<!DOCTYPE html>
<html>
<head>
    <title>Alpaca Ascom Drivers Setup</title>
    <meta charset="UTF-8">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/jquery-ui.min.css">
    <link rel="stylesheet" href="/css/theme.css">

    <script src="/js/jquery.min.js"></script>
    <script src="/js/jquery-ui.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <script src="/js/jsonFormer.jquery.js"></script>

 </head>
 <body>
    <div class="container">
        <div class="card mb-3 mt-3">
            <div class="card-header">
                <div id="title"><H3>Alpaca Ascom Drivers Setup</H3></div>
                <ul id="nav-links" class="nav nav-tabs card-header-tabs">
                </ul>
            </div>
        <div class="card-body">
            <div id="form-container"></div>
            <button type="button" id="json_refresh" class="btn btn-primary">Refresh</button>
            <button type="button" id="json_update" class="btn btn-primary">Update</button>
            <button type="button" id="json_save" class="btn btn-primary">Save</button>
        </div>
    </div>
    <script>
        $(document).ready(function () 
        {
            $.ajaxSetup({ cache: false });
            $.getJSON("jsondata", function(data) {
                $('#form-container').jsonFormer({
                    title: "Setup",
                    jsonObject: data
                });
                data;       
            });


            $("#json_update").click(function (event) {
                $.ajax({
                    url: 'jsondata',
                    //type: 'POST',
                    type:'POST',
                    dataType: "json",
                    //data: JSON.stringify($('#form-container').jsonFormer('formData')),
                    data: JSON.stringify($('#form-container').jsonFormer('getDiff')),
                    contentType: 'application/json',
                    success: function(msg) {alert(" Update done")

                    }
                })
            });


            $("#json_save").click(function () {
                $.getJSON("/save_settings", function(data) {
                    alert(data['saved'] == true? "Saved succesfully" : "Save failed!");
                })
            });
            $("#json_refresh").click(function () {
                location.reload(); // until json-only refresh is ready
            });
            $.getJSON("/links", function(data) {
                let path = window.location.pathname;
                for(name in data) {
                    let url = data[name];
                    let navitem = $('<li class="nav-item"><a class="nav-link" href="#"></a></li>');
                    let a = navitem.find("a");
                    a.attr('href', url).text(name);
                    if(path == url)
                        a.addClass('active');
                    $("#nav-links").append(navitem);
                }
            });
        });
    </script>
   </body>
 </html>

React Native metro-symbolicate Not Resolving Stack Trace to Actual Source Files

I’m working on a React Native project, and I’m trying to symbolicate JavaScript crash stack traces to get human-readable file names using metro-symbolicate. However, when I pass the stack trace to metro-symbolicate, it still returns index.android.bundle instead of the actual source file names like App.js or HomeScreen.js.

I’m capturing JavaScript errors in my React Native app using ErrorUtils.setGlobalHandler, and I’m trying to symbolicate the stack trace using metro-symbolicate to get readable file names instead of index.android.bundle.

However, when I pass the stack trace to metro-symbolicate, it still returns index.android.bundle instead of actual source file names like App.js or HomeScreen.js.

The code where I get the global JS Errors:

ErrorUtils.setGlobalHandler(async (error: Error, isFatal: boolean) => {
      Logger.instance.log(`JS Runtime Error: ${error.message}`);

      if (!error.stack) {
        Logger.instance.log('No stack trace available.');
        return;
      }
      this.logError(error.message, error.stack, isFatal);
    });

Backend Code where I handle the request for “symbolicate” route.

app.post('/symbolicate', async (req, res) => {
    const { stack } = req.body;

    if (!stack || !Array.isArray(stack)) {
        return res.status(400).json({ error: 'Invalid stack trace format' });
    }

    try {
        await fs.access(SOURCE_MAP_PATH);

        const stackJson = JSON.stringify({ stack });

        const symbolicateProcess = spawn('npx', ['metro-symbolicate', SOURCE_MAP_PATH]);

        let output = '';
        let errorOutput = '';

        symbolicateProcess.stdin.write(stackJson);
        symbolicateProcess.stdin.end();

        symbolicateProcess.stdout.on('data', (data) => {
            output += data.toString();
        });

        symbolicateProcess.stderr.on('data', (data) => {
            errorOutput += data.toString();
        });

        symbolicateProcess.on('close', (code) => {
            if (code === 0) {
                try {
                    const parsedOutput = JSON.parse(output);
                    return res.json({ readableStack: parsedOutput.stack });
                } catch (jsonError) {
                    return res.status(500).json({ error: 'Failed to parse symbolicated stack trace' });
                }
            } else {
                return res.status(500).json({ error: 'Failed to symbolicate stack trace', details: errorOutput });
            }
        });

    } catch (error) {
        return res.status(500).json({ error: 'Failed to process request' });
    }
});

What I Expect After Symbolication

{
  "readableStack": [
    {
      "file": "App.js",
      "lineNumber": 123,
      "column": 20
    }
  ]
}

This is what I actually get:

{
  "stack": [
    {
      "file": "index.android.bundle",
      "lineNumber": 123199,
      "column": 20
    }
  ]
}

How to test routes in vitest

I want to test if a link opens another page and that page has the correct elements in it like heading etc. I cannot render my routes in the tests.

// Setup
const setup = () => {
  const router = createMemoryRouter(routes, {
    initialEntries: ['/'],
  })
  return {
    user: userEvent.setup(),
    router: router,
    ...render(<RouterProvider router={router} />),
  }
}

// error
//    7444|     ...rest                                                                                                                              
//    7445|   }, forwardedRef) {                                                                                                                     
//    7446|     let { basename } = React10.useContext(NavigationContext);                                                                            
//       |           ^                                                                                                                               
//    7447|     let isAbsolute = typeof to === "string" && ABSOLUTE_URL_REGEX2.test(to);                                                             
//    7448|     let absoluteHref;   

routes.jsx

import CartPage from './ShoppingCart-App/pages/CartPage.jsx'
import App from './ShoppingCart-App/App.jsx'

const routes = [
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: 'cart-page',
        element: <CartPage />,
      },
    ],
  },
]

export { routes }

I was able to render it when changed my setup to this

    ...render(<RouterProvider router={router} />, {wrapper: MemoryRouter}),

but on clicking the link path doesn’t update
my test

expect(window.location.pathname).toBe('/cart-page') // received /

when I render like this then path updates

    ...render(<RouterProvider router={router} />, {wrapper: BrowserRouter}),

but now clicking link doesn’t update the outlet in app.

app.jsx

    <div>
      <header>
        <h1>Shopping Cart</h1>
        <Link to="cart-page" aria-label="cart page">
          <div data-testid="cart-counter">{cart.length}</div>
        </Link>
        <Categories add={addFilter} remove={removeFilter} />
      </header>

      <Outlet />

      <section data-testid="products-container">
        <h2>Products</h2>{' '}
        <ul>
          {products.map((product) => {
            if (filter.length !== 0 && !filter.includes(product.category))
              return null

            return (
              <li key={product.id}>
                <Product
                  product={product}
                  onImageLoad={onLoad}
                  toggleProduct={updateCart}
                />
              </li>
            )
          })}
        </ul>
      </section>
    </div>

In dev mode everything works.

There is a delay before performing a scroll to a section (GSAP)

I’m new to JS, especially working with gsap. I want to perform scrolling to certain sections via gsap ScrollTo. I want to make a smooth and not very fast scrolling. I also want to move elements in parallel with scrolling, creating a parallax effect. Maybe someone has encountered such a thing? Or recommend something to replace gsap. Very much need help.

My HTML:

<!DOCTYPE html>
<html lang="uk">

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

    <link rel="icon" type="image/x-icon" href="./static/img/favicon-32x32.ico" sizes="32x32">
    <link rel="icon" type="image/x-icon" href="./static/img/favicon-192x192.ico"
        sizes="192x192">
    <link rel="apple-touch-icon" href="./static/img/favicon-192x192.ico">

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Geologica:[email protected]&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollToPlugin.min.js"></script>
    <link rel="stylesheet" href="./static/css/style.css">
</head>
<body>
    <main class="main">
        <div class="section-top">
            <section class="welcome">
                <div class="block-logo animated ">
                    <img src="./static/img/logo.svg" alt="Logo">
                    <span>TEXT</span>
                </div>
                <div class="wrap-block-kan animated ">
                    <div class="block-kan">
                        <span>text</span>
                        <img src="./static/img/logo-kan.svg" alt="">
                    </div>
                </div>
                <picture>
                    <source srcset="./static/img/pattern-left-1-full.png"
                        media="(min-width: 576px)">
                    <img src="./static/img/mob/pattern-left-1.png" alt="Pattern"
                        class="pattern-left animated">
                </picture>
                <picture>
                    <source srcset="./static/img/pattern-right-1-full.png"
                        media="(min-width: 576px)">
                    <img src="./static/img/mob/pattern-right-1.png" alt="Pattern"
                        class="pattern-right animated">
                </picture>
            </section>
            <section class="comming-soon">
                <div class="container">
                    <h2 class="block-title animated ">TEXT <span class="text-blue">ТВІЙ</span>
                        <span class="img-rutm"><img src="./static/img/rutm-text.svg"
                                alt="Logo rutm"></span>TEXT!
                    </h2>
                    <div class="counter animated ">
                        <div class="element-count day">
                            <div class="number">07</div>
                            <span class="num-text">DAYS</span>
                        </div>
                        <div class="element-count hour">
                            <div class="number">11</div>
                            <span class="num-text">HOUR</span>
                        </div>
                        <div class="separator">:</div>
                        <div class="element-count minute">
                            <span class="number">01</span>
                            <span class="num-text">MIN</span>
                        </div>
                        <div class="separator gray">:</div>
                        <div class="element-count second">
                            <div class="number">02</div>
                            <span class="num-text">SEC</span>
                        </div>
                    </div>
                </div>
            </section>
            <section class="block-img-top">
                <img src="./static/img/mob/img-2.webp" alt="Photo kvartal 1"
                    class="main-block-img">
                <img src="./static/img/mob/pattern-left-2.png" alt="Pattern"
                    class="pattern-left animated">
            </section>
            <section class="block-desc">
                <div class="container">
                    <p class="animated paragraph-1">
                        TEXT
                    </p>
                    <p class="animated">
                        TEXT
                    </p>
                    <br>
                    <p class="animated paragraph-2"><strong>«TEXT»</strong> TEXT
                    </p>
                </div>
                <img src="./static/img/mob/pattern-right-2.png" alt="Pattern"
                    class="pattern-right animated">
            </section>
        </div>
    </main>
    <script src="./static/js/scripts.js"></script>
</body>

</html>

My code CSS:

body {
  font-family: "Geologica", serif;
  color: var(--color-font-1);
  font-optical-sizing: auto;
  font-weight: 400;
  font-style: normal;
  font-size: 16px;
  background-color: #000000;
  overflow: hidden;
}

@media screen and (min-width: 576px) {
  body {
    overflow: auto;
  } 
}

body.show-scrollbar {
  overflow-y: scroll;
}

h1,
h2,
h3 {
  text-transform: uppercase;
}

h2 {
  font-size: 31px;
  font-weight: 100;
  letter-spacing: 0.1em;
  line-height: 36px;
}

h3 {
  font-size: 1.563rem;
  font-weight: 100;
  line-height: 28px;
}

main {
  overflow: hidden;
}

.container {
  padding: 0 4rem 0 2.125rem;
}

.pattern-left,
.pattern-right {
  position: absolute;
}

.section-top {
  position: relative;
}

.text-blue {
  color: var(--color-blue);
}

.text-yellow {
  color: var(--color-yellow);
}

.text-green {
  color: var(--color-green);
}

.text-orange {
  color: var(--color-orange);
}

.text-red {
  color: var(--color-red);
}


/* welcome */
.welcome {
  position: relative;
  height: calc(100vw* 2.38);
  width: auto;
  background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 80%, rgba(0, 0, 0, 0.9) 100%), url("../img/mob/img-1.webp");
  background-repeat: no-repeat;
  background-origin: content-box;
  background-size: cover;
  z-index: 2;
}

@media screen and (min-width: 576px) {
  .welcome {
    height: calc(100vw* 1.43);
    background-image: url("../img/img-1-full.webp");
  }
}

.welcome .wrap-block-kan {
  display: flex;
  justify-content: right;
  padding-top: 2.875rem;
  padding-right: 12%;
}

.welcome .block-kan {
  position: relative;
}

.welcome .block-kan img {
  width: 5.25rem;
}

.welcome .block-kan span {
  position: absolute;
  top: 0.625em;
  left: -0.75em;
  font-size: 0.7em;
  font-weight: 100;
  text-transform: uppercase;
  letter-spacing: 1px;
}

.welcome .pattern-left {
  width: 18.67%;
  left: -4.8vw;
  top: 22vw;
}

.welcome .pattern-right {
  width: 25vw;
  top: 135vw;
  right: -14%;
}

.block-logo {
  padding-top: 2.5rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 1;
  transition: opacity .8s ease, scale 1s ease, transform .01s ease;
}

.block-logo img {
  margin-bottom: 0.75em;
  max-width: 53%;
}

.block-logo span {
  font-size: .7em;
  font-weight: 300;
  letter-spacing: 4px;
  text-transform: uppercase;
}

@media screen and (min-width: 576px) {
  .welcome .block-logo img {
    margin-bottom: .8em;
    max-width: 25%;
  }

  .welcome .block-logo span {
    font-size: .6em;
  }

  .welcome .pattern-left {
    width: 10.55vw;
    left: 0;
    top: 62.5vw;
  }

  .welcome .pattern-right {
    width: 6.72vw;
    right: 0;
    top: 18.92vw;
  }
}

@media screen and (min-width: 768px) {
  .welcome .block-logo img {
    margin-bottom: 1.1em;
  }

  .welcome .block-logo span {
    font-size: .7em;
    letter-spacing: 5px;
  }

  .welcome .wrap-block-kan {
    padding-top: 2.375rem;
    padding-right: 27%;
  }
}

@media screen and (min-width: 992px) {
  .welcome .block-logo span {
    font-size: .9em;
    letter-spacing: 6px;
  }

  .welcome .wrap-block-kan {
    padding-right: 29%;
  }
}

@media screen and (min-width: 1200px) {
  .welcome .block-logo span {
    font-size: 1.1vw;
    letter-spacing: 8px;
  }

  .welcome .wrap-block-kan {
    padding-right: 31vw;
  }
}

@media screen and (min-width: 1400px) {
  .welcome .block-logo {
    padding-top: 3.75vw;
  }

  .welcome .block-logo img {
    margin-bottom: 1.4em;
  }

  .welcome .block-logo span {
    font-size: 1.3vw;
  }

  .welcome .wrap-block-kan {
    padding-top: 2.9vw;
    padding-right: 30.1vw;
  }

  .welcome .block-kan img {
    width: 7vw;
  }

  .welcome .block-kan span {
    font-size: .9vw;
  }
}


/* comming-soon */
.comming-soon {
  position: relative;
  margin-top: -4.125rem;
  margin-bottom: -1.25rem;
  z-index: 2;
}

.comming-soon .container {
  padding: 0 1.65rem;
}

.comming-soon .block-title {
  margin-bottom: 2rem;
  padding: 0 1.875rem 0 .25rem;
  position: relative;
  z-index: 1;
}

.comming-soon .block-title .img-rutm {
  display: inline-block;
  width: 8rem;
  margin-right: .5em;
  margin-left: .1em;
}

@media screen and (min-width: 768px) {
  .comming-soon {
    position: absolute;
    top: 20rem;
    right: 8%;
    max-width: 40%;
  }

  .comming-soon .container {
    padding: 0;
  }

  .comming-soon .block-title {
    margin-bottom: 1rem;
    padding: 0;
    font-size: 1.2rem;
  }

  .comming-soon .block-title .img-rutm {
    width: 25%;
    margin-bottom: -2px;
  }
}

@media screen and (min-width: 992px) {
  .comming-soon {
    top: 23rem;
    right: 10%;
    max-width: 37%;
  }

  .comming-soon .block-title {
    font-size: 1.45rem;
    margin-bottom: 1.5rem;
  }
}

@media screen and (min-width: 1200px) {
  .comming-soon {
    right: 17%;
    max-width: 30%;
  }
}

@media screen and (min-width: 1400px) {

  .comming-soon {
    top: 29vw;
    right: 18%;
    max-width: 29%;
  }

  .comming-soon .block-title {
    font-size: 1.875vw;
    line-height: 120%;
  }

  .comming-soon .block-title .img-rutm {
    width: 27%;
    margin-bottom: -1px;
  }
}


/* counter */
.counter {
  display: flex;
  justify-content: space-between;
  text-align: center;
  font-size: 3.313rem;
}

.counter .element-count {
  display: flex;
  flex-direction: column;
  min-width: 22%;
}

.counter .element-count.day {
  min-width: auto;
}

.main .counter .element-count.day {
  margin-right: 14px;
}

.counter .second .number,
.counter .separator.gray {
  color: var(--color-gray);
}

.counter .separator.gray {
  padding-left: 5px;
}

@media screen and (min-width: 768px) {

  .counter .second .number,
  .counter .separator.gray {
    color: var(--color-white);
    opacity: .5;
  }
}

.counter .number {
  letter-spacing: 1.32px;
}

.counter .separator {
  margin-top: -4px;
  margin-left: -5px;
}

.counter .num-text {
  color: var(--color-gray);
  font-size: 6px;
  letter-spacing: 0.18px;
  font-weight: 100;
}

@media screen and (min-width: 576px) {
  .main .counter {
    width: 100%;
  }

  .counter .number {
    font-size: 4rem;
    letter-spacing: 1.4px;
  }

  .counter .num-text {
    font-size: 7px;
    letter-spacing: 0.25px;
    color: var(--color-white);
    opacity: 0.6;
  }

  .counter .separator {
    margin-left: -5px;
  }
}

@media screen and (min-width: 768px) {
  .counter .number {
    font-size: 3.5rem;
    letter-spacing: 1.62px;
  }

  .counter .num-text {
    font-size: .5rem;
    letter-spacing: 0.35px;
  }

}

@media screen and (min-width: 992px) {
  .counter .separator {
    margin-top: 0;
  }
}

@media screen and (min-width: 1400px) {
  .main .counter {
    font-size: 3.8vw;
  }

  .counter .num-text {
    font-size: 0.7rem;
    letter-spacing: 0.45px;
  }

  .counter .number {
    font-size: 4.4vw;
    letter-spacing: 3.08px;
  }
}

/* block-img-top */

.block-img-top {
  position: relative;
  z-index: 1;
}

.block-img-top .pattern-left {
  height: 52%;
  bottom: 5%;
  left: -1.5rem;
}

@media screen and (min-width: 768px) {
  .block-img-top {
    display: none;
  }
}

/* block-desc */
.block-desc {
  font-size: 1.063rem;
  font-weight: 100;
  line-height: 28px;
  padding-bottom: .98rem;
  position: relative;
  z-index: 3;
}

.block-desc .pattern-right {
  height: 17rem;
  top: -2.156rem;
  right: -3.5rem;
}

@media screen and (min-width: 576px) {
  .block-desc {
    max-width: 62.5%;
    font-weight: 100;
    font-size: 1.1rem;
    line-height: 36px;
    margin: -18% auto 0;
  }

  .block-desc .container {
    padding: 0;
  }

  .block-desc .pattern-right {
    display: none;
  }
}

@media screen and (min-width: 1400px) {
  .block-desc {
    line-height: 1.9vw;
    font-size: 1.4rem;
  }
}

My JS:

document.addEventListener("DOMContentLoaded", (event) => {
  const welcome = document.querySelector(".welcome");
  const logo_head = document.querySelector(".welcome .block-logo");
  const kan_logo_head = document.querySelector(".welcome .block-kan");
  const welcome_p_left = document.querySelector(".welcome .pattern-left");
  const welcome_p_right = document.querySelector(".welcome .pattern-right");
  const comming_soon = document.querySelector(".comming-soon");
  const block_img_top = document.querySelector(".block-img-top");
  
  gsap.registerPlugin(ScrollTrigger, ScrollToPlugin);

  // enable only on mobile
  if (window.innerWidth < 768) {
    /* SWITCH BEETWEEN SECTIONS */

    // list Of section
    const sections = gsap.utils.toArray([
      ".welcome",
      ".comming-soon",
      ".block-img-top",
      ".block-desc .paragraph-1",
      ".block-desc .paragraph-2",
      ".zones .pool-spa",
      ".zones .bbq-roof",
      ".zones .school",
      ".footer",
    ]);

    let currentIndex = 0; // current index section
    let isScrolling = false; // Flag to prevent multiple scrolls

    function scrollToSection(index) {
      if (isScrolling || index < 0 || index >= sections.length) return;

      isScrolling = true;
      let targetSection = sections[index];

      let offset = 30; // Default top of section
      if (index === 1) offset = window.innerHeight / 2 - 30; // For .comming-soon the center of the screen

      gsap.to(window, {
        duration: 1,
        scrollTo: { y: targetSection, offsetY: offset },
        ease: "sine.out",
        onComplete: () => {
          isScrolling = false;
          currentIndex = index;

          // Show the standard scroll bar on the last section only
          if (currentIndex === sections.length - 1) {
            document.body.classList.add("show-scrollbar");
          } else {
            document.body.classList.remove("show-scrollbar");
          }
        },
      });
    }

    let lastScrollTime = 0;
    const scrollDelay = 500; // Delay between scrolls in milliseconds

    function handleScroll(event) {
      const currentTime = new Date().getTime();

      if (isScrolling || currentTime - lastScrollTime < scrollDelay) {
        return;
      }

      lastScrollTime = currentTime;

      // Override the default scrolling behavior
      if (currentIndex !== sections.length - 1) {
        event.preventDefault();
      }

      let scrollAmount = event.deltaY || event.touches?.[0]?.clientY; // Defining the input type
      if (Math.abs(scrollAmount) < 15) return; // Filter out movements that are too small

      if (scrollAmount > 0 && currentIndex < sections.length - 1) {
        // scroll up
        scrollToSection(currentIndex + 1);
      } else if (scrollAmount < 0 && currentIndex > 0) {
        // scroll down
        // Check if it is the last section and its top is not at the top of the window
        if (currentIndex === sections.length - 1) {
          let lastSectionTop =
            sections[currentIndex].getBoundingClientRect().top;
          if (lastSectionTop < 0) {
            // If the top of the last section is not at the top of the window, do not allow scrolling upwards
            return;
          } else {
            event.preventDefault();
            document.body.classList.remove("show-scrollbar");
          }
        }
        scrollToSection(currentIndex - 1);
      }
    }

    // Track mouse scroll and touch events
    let touchStart = 0;

    window.addEventListener("wheel", handleScroll, { passive: false });
    window.addEventListener(
      "touchstart",
      (e) => (touchStart = e.touches[0].clientY)
    );
    window.addEventListener("touchmove", (e) => {
      let touchEnd = e.touches[0].clientY;
      let delta = touchStart - touchEnd;
      handleScroll({
        deltaY: delta,
        touches: e.touches,
        preventDefault: () => e.preventDefault(),
      });
    });

    // parallax effect for another elements
    gsap.to(kan_logo_head, {
      y: "-20rem",
      x: "20rem",
      duration: 0.5,
      ease: "sine.inOut",
      scrollTrigger: {
        trigger: comming_soon,
        start: "top bottom",
        end: "bottom top",
        toggleActions: "play none none reverse",
      },
    });

    gsap.to(logo_head, {
      y: "58vh",
      opacity: 0,
      scale: 1.5,
      ease: "sine.inOut",
      scrollTrigger: {
        trigger: comming_soon,
        start: "top bottom",
        end: "bottom top",
        toggleActions: "play none none reverse",
      },
    });

    gsap.to(comming_soon, {
      marginTop: "-12rem",
      duration: 0.3,
      ease: "sine.inOut",
      scrollTrigger: {
        trigger: welcome,
        start: "bottom 80%",
        end: "bottom 40%",
        toggleActions: "play none none reverse",
      },
    });

    gsap.to(welcome_p_right, {
      y: "-150vw",
      duration: 0.5,
      ease: "sine.inOut",
      scrollTrigger: {
        trigger: comming_soon,
        start: "top bottom",
        end: "bottom top",
        toggleActions: "play none none reverse",
      },
    });

    gsap.to(welcome_p_left, {
      y: "206vw",
      duration: 0.5,
      ease: "sine.inOut",
      scrollTrigger: {
        trigger: comming_soon,
        start: "top bottom",
        end: "bottom top",
        toggleActions: "play none none reverse",
      },
    });
  }
 });

How to write a string from 2D array containing string and number types without using loops

I have an array ['a18', [['25', 0], ['24', 3]]] and want to write out a string in the following format:

[ [' 25', 0 ], [' 24', 3 ] ]

I did the following and it works.

Is there another (nicer) possibility to obtain a string output using a map, filter or reduce function?

const a = ["a18", [['25', 0], ['24', 3]]]
let outstring = "[ "
for (let i = 0; i < a[1].length; i++) {
  outstring = outstring + "[ '" + a[1][i][0] + "', " + a[1][i][1].toString() + " ], "
}
console.log("outstring ", outstring.substring(0, outstring.length - 2) + "]") 

Search a JSON multidimensional array and return values when keys aren’t empty [duplicate]

I have the following JSON

{
    "id": 438473847,
    "name": "DEPARTMENT ",
    "children": [
        {
            "id": 658567685,
            "name": "SALES",
            "children": [
                {
                    "id": 768976,
                    "name": "ARIZONA",
                    "children": [
                        {
                            "id": 6978697,
                            "name": "PHOENIX",
                            "children": [],
                            "jobs": []
                        },
                        {
                            "id": 87967967008,
                            "name": "MESA",
                            "children": [],
                            "jobs": []
                        },
                        {
                            "id": 8799787,
                            "name": "SEGUNDO",
                            "children": [
                                {
                                    "id": 67896789678,
                                    "name": "WAREHOUSE1",
                                    "children": [],
                                    "jobs": []
                                },
                            ],
                            "jobs": [
                                {
                                    "absolute_url": /system/jobs/768976",
                                    "education": "yes",
                                    "internal_job_id": 568596,
                                    "location": {
                                        "name": "CA"
                                    },
                                    "metadata": null,
                                    "id": 8697869,
                                }
                            ]
                        }
                    ]
                }
            ],
            "jobs": [
                {
                    "absolute_url": /system/jobs/7698769",
                    "education": "yes",
                    "internal_job_id": 65655,
                    "location": {
                        "name": "AZ"
                    },
                    "metadata": null,
                    "id": 5644445,
                }
            ]
        }
    ],
    "jobs": []
}

and I would like to create a new object that has the results from all jobs node but only those that aren’t empty. The idea is that I get a list of jobs with their properties, from this massive JSON. This needs to be done with JavaScript or jQuery.

I tried doing recursive search but nothing worked.

closest() and parentElement don’t see parent [closed]

On the YouTube site I need to get from

el1 = document.querySelector('#content') 
// before 
el2 = document.querySelector('ytd-rich-item-renderer').  

el2.children returns el1 in the browser console, but el1.closest('ytd-rich-item-renderer') returns null, and el1.parentElement is a completely different element, which is structurally located above el2

enter image description here