Table spacing and border combination

I am working on a React table UI where each row can be expanded to show additional details. The design requires:

  • When a row expands, it should visually merge with the expanded content below it.
  • The expanded content should maintain the same border as the parent row.
  • No extra spacing or gaps should appear between the main row and the expanded row.

The challenge I am facing is:

  • Extra spacing appears between the main row and the expanded content.
  • Borders don’t align properly when expanded.
  • If I remove border-spacing, then the entire table styling breaks.

I have tried:

  • Setting border-spacing: 0; and border-collapse: collapse; (caused styling issues).
  • Using negative margins (margin-top: -1px;) on the expanded row (still inconsistent).
  • Ensuring border-bottom: none; on the main row when expanded (didn’t fully solve it).
import React, { useCallback, useMemo, useState } from "react";
import "./index.scss";
import InfiniteScroll from "react-infinite-scroll-component";
import Lottie from "react-lottie";
import { fourDotsAnimations } from "../../assets/lottie-animations/animationOptions";
import CustomIcon from "../../assets/icons";
import {
  getFromLocalStorage,
  LocalStorageEnum,
} from "../../utils/local-storage";
import { useTranslation } from "react-i18next";

import moment from "moment-timezone";

const itemMasterTableHeaderKeys = [
  {
    title: "prNumber",
    key: "prNumber",
    sortable: true,
    styles: {
      width: "10%",
      maxWidth: "10%",
    },
  },
  {
    title: "Description",
    key: "Description", // Sorting key
    sortable: false,
    styles: {
      width: "20%",
      maxWidth: "20%",
    },
  },
  {
    title: "Date",
    key: "date",
    sortable: true,
    styles: {
      width: "15%",
      maxWidth: "15%",
    },
  },
  {
    title: "Status",
    key: "Status",
    sortable: true,
    styles: {
      width: "10%",
      maxWidth: "10%",
    },
  },

  {
    title: "TotalCost",
    key: "TotalCost",
    sortable: true,
    styles: {
      width: "10%",
      maxWidth: "10%",
    },
  },
  {
    title: "Actions",
    key: "Actions",
    sortable: false,
    styles: {
      width: "5%",
      maxWidth: "5%",
    },
  },
];

function PrScreen() {
  const { t } = useTranslation();
  const timeZone =
    JSON.parse(
      getFromLocalStorage(LocalStorageEnum.SELECTED_STORE) || ""
    )?.country?.toUpperCase() === "CANADA"
      ? "America/Toronto"
      : "Africa/Tunis";

  const [sortConfig, setSortConfig] = useState<{
    key: string | null;
    direction: "asc" | "desc" | null;
  }>({
    key: null,
    direction: null,
  });

  const [expandedRows, setExpandedRows] = useState<number[]>([]);

  const toggleExpandedRow = (index: number) => {
    setExpandedRows((prev) => {
      if (prev.includes(index)) {
        return prev.filter((row) => row !== index);
      }
      return [...prev, index];
    });
  };

  const handleSort = useCallback((key: string) => {
    setSortConfig((prev) =>
      prev.key === key
        ? { key, direction: prev.direction === "asc" ? "desc" : "asc" }
        : { key, direction: "asc" }
    );
  }, []);

  const transactionsHistory = Array.from({ length: 10 }, (_, i) => ({
    prNumber: `20359${i + 1}`,
    description: "Lorem ipsum dolor sit amet...",
    date: moment().subtract(i, "days").format("YYYY-MM-DD"),
    status: i % 2 === 0 ? "Placed" : "Completed",
    totalCost: "352 tnd",
  }));

  const sortedData = useMemo(() => {
    if (!sortConfig.key || !sortConfig.direction) return transactionsHistory;
    return [...transactionsHistory].sort((a: any, b: any) => {
      const aValue = a[sortConfig?.key || ""];
      const bValue = b[sortConfig?.key || ""];

      if (typeof aValue === "string" && typeof bValue === "string") {
        return sortConfig.direction === "asc"
          ? aValue.localeCompare(bValue)
          : bValue.localeCompare(aValue);
      }
      if (typeof aValue === "number" && typeof bValue === "number") {
        return sortConfig.direction === "asc"
          ? aValue - bValue
          : bValue - aValue;
      }
      return 0;
    });
  }, [
    // transactionsHistory,

    sortConfig,
  ]);

  const handleLoadMoreData = () => {
    console.log("load more");
  };

  return (
    <div className="pr-screen-container">
      <div className="pr-screen-header-container">
        <span>select store here</span>

        <form>
          <span>search here</span>
        </form>
      </div>
      <div className="pr-screen-content-container" id="scrollableDiv">
        <InfiniteScroll
          dataLength={10}
          next={handleLoadMoreData}
          hasMore={true}
          loader={
            <div className="item-master-table-loading-pagination">
              {true ? (
                <></>
              ) : (
                <Lottie
                  options={fourDotsAnimations}
                  height={"10%"}
                  width={"8%"}
                />
              )}
            </div>
          }
          scrollableTarget="scrollableDiv"
        >
          <table className="pr-screen-table-container">
            <thead>
              <tr className="pr-screen-table-tr">
                {itemMasterTableHeaderKeys.map((key, index) => (
                  <th
                    key={index}
                    className={`pr-screen-table-th ${
                      key.sortable ? "cursor" : ""
                    }`}
                    style={key.styles}
                    onClick={() => key.sortable && handleSort(key.key)}
                  >
                    <div className="pr-screen-table-th-icons-container">
                      <span>{t(key.title)}</span>
                      {key.sortable && (
                        <CustomIcon
                          className="pr-screen-table-th-icons-style"
                          icon={
                            sortConfig.key === key.key
                              ? sortConfig.direction === "asc"
                                ? "sort-asc"
                                : "sort-desc"
                              : "sort-default"
                          }
                          size={9}
                        />
                      )}
                    </div>
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {!sortedData?.length ? (
                <tr>
                  <td
                    colSpan={1000}
                    rowSpan={4}
                    className="pr-screen-table-tr-no-items"
                  >
                    <div className="pr-screen-table-no-items-container">
                      <CustomIcon icon="no-data-found" size={100} />
                      <span className="pr-screen-table-no-items-txt">
                        {t("No pr found !")}
                      </span>
                    </div>
                  </td>
                </tr>
              ) : (
                sortedData.map((transaction: any, index) => (
                  <React.Fragment key={index}>
                    <tr
                      className={`pr-screen-table-tr-body ${
                        expandedRows.includes(index) ? "expanded" : ""
                      }  ${
                        expandedRows.includes(index - 1) ? "beforeExpanded" : ""
                      } `}
                    >
                      <td
                        onClick={() => toggleExpandedRow(index)}
                        className="pr-screen-table-td-body"
                      >
                        <CustomIcon
                          icon={
                            expandedRows.includes(index)
                              ? "chevron-up"
                              : "chevron-down"
                          }
                          size={10}
                        />
                        {transaction.prNumber}
                      </td>
                      <td className="pr-screen-table-td-body">
                        {transaction.description}
                      </td>
                      <td className="pr-screen-table-td-body">
                        {moment
                          .utc(transaction.date)
                          .tz(timeZone)
                          .format("DD/MM/YYYY")}
                      </td>
                      <td className="pr-screen-table-td-body">
                        {transaction.status}
                      </td>
                      <td className="pr-screen-table-td-body">
                        {transaction.totalCost}
                      </td>
                      <td className="pr-screen-table-td-body-actions">
                        <div
                          className={`pr-screen-table-td-actions-container ${
                            false ? "disabled" : ""
                          }`}
                        >
                          {true ? (
                            <CustomIcon icon="pr-action-cancel" size={18} />
                          ) : (
                            <>
                              <div className="pr-icon-cancel-no-hover">
                                <CustomIcon icon="pr-action-cancel" size={18} />
                              </div>
                              <div className="pr-icon-cancel-with-hover">
                                <CustomIcon
                                  icon="pr-action-cancel-white"
                                  size={18}
                                />
                              </div>
                            </>
                          )}
                        </div>
                      </td>
                    </tr>

                    {expandedRows.includes(index) && (
                      <tr
                        style={{
                          borderSpacing: 100,
                          padding: 1000,
                        }}
                      >
                        <td colSpan={5}>
                          <div className={`pr-table-expanded-row`}>
                            <div>
                              <h4>Details</h4>
                              <p>
                                This is a collapsible section where you can add
                                anything—extra details, forms, buttons, etc.
                              </p>
                              <button className="action-button">
                                Do Something
                              </button>
                            </div>
                          </div>
                        </td>
                      </tr>
                    )}
                  </React.Fragment>
                ))
              )}
            </tbody>
          </table>
        </InfiniteScroll>
      </div>
    </div>
  );
}

export default PrScreen;

.pr-screen-container {
  display: flex;
  flex-direction: column;
  margin: 1rem 1rem 0rem 4rem;
  height: 86vh;
  .pr-screen-header-container {
    display: flex;
    flex-direction: column;
  }
  .pr-screen-content-container {
    overflow: auto;
    overflow-y: auto; // Allows scrolling for both header and items
    height: 100vh;
    .pr-screen-table-container {
      width: 60%;
      text-align: left;
      user-select: none;
      -moz-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
      border-width: 100px;

      .pr-screen-table-tr {
        .pr-screen-table-th {
          font-weight: 300;
          font-size: 12px;
          color: #aaaaaa;
          text-overflow: ellipsis;
          padding: 0.5rem;
          padding-left: 1rem;
          background: white;
          &.cursor {
            cursor: pointer;
          }

          .pr-screen-table-th-icons-container {
            display: flex;
            align-items: center;
            .pr-screen-table-th-icons-style {
              margin-left: 0.7rem;
            }
          }
        }
      }
      .pr-table-expanded-row {
        border: 1px solid red;
        padding: 1rem;
        width: 100%;
      }
      .pr-screen-table-tr-body {
        border-radius: 10px;

        &.expanded {
          vertical-align: text-top;
          &:nth-child(odd) {
            border-color: transparent;
            border-bottom: 0px solid transparent;
          }
          &:nth-child(even) {
            border-color: transparent;
            border-bottom: 10px solid transparent;
          }
          td:not(:last-child) {
            &:first-child {
              border-top-left-radius: 0rem;
              border-bottom-left-radius: 0rem;
            }

            &:nth-child(5) {
              border-top-right-radius: 0rem;
              border-bottom-right-radius: 0rem;
            }
          }

          td:first-child {
            border-left: 0.5px solid #ad9e89;
            border-top: 0.5px solid #ad9e89;
          }
          td:nth-child(5) {
            border-right: 0.5px solid #ad9e89;
            border-top: 0.5px solid #ad9e89;
            border-top-right-radius: 0rem;
            border-bottom-right-radius: 0rem;
            border-radius: 0rem;
          }
          td:not(:last-child) {
            border-top: 0.5px solid #ad9e89;
          }
        }

        &:nth-child(odd) {
          background: rgba(252, 251, 251, 1);
          border-color: transparent;
          border-bottom: 20px solid transparent;
        }
        &:nth-child(even) {
          border-color: transparent;
          border-bottom: 20px solid transparent;
        }

        td:not(:last-child) {
          &:first-child {
            border-top-left-radius: 0.4rem;
            border-bottom-left-radius: 0.4rem;
          }

          &:nth-child(5) {
            border-top-right-radius: 0.4rem;
            border-bottom-right-radius: 0.4rem;
          }
        }

        td:last-child {
          background: white;
        }

        td:not(:nth-last-child(-n + 2)) {
          border-right: 1px solid #e0e0e0;
        }

        .pr-screen-table-td-body {
          font-weight: 400;
          font-size: 14px;
          text-overflow: ellipsis;
          overflow: hidden;
          white-space: nowrap;
          max-width: 0;
          padding: 0.5rem 0.4rem;
          padding-left: 1rem;
          &.center {
            text-align: center;
            padding-left: 0rem;
          }
          &.bold {
            font-weight: 600;
          }
        }
        .pr-screen-table-td-body-actions {
          font-weight: 400;
          font-size: 14px;
          text-overflow: ellipsis;
          overflow: hidden;
          white-space: nowrap;
          max-width: 0;
          padding: 0rem;
          padding-left: 1rem;
          .pr-screen-table-td-actions-container {
            display: flex;
            align-content: center;
            align-items: center;
            border: 1px solid #eeeeee;
            padding: 0.6rem;
            border-radius: 0.4rem;
            width: fit-content;
            background-color: transparent;
            cursor: pointer;
            &.disabled {
              cursor: not-allowed;
              border: 1px solid #ffffffb9;
            }
            .pr-icon-cancel-no-hover {
              display: flex;
            }
            .pr-icon-cancel-with-hover {
              display: none;
            }
            &:hover {
              background-color: #ea504f;
              .pr-icon-cancel-no-hover {
                display: none;
              }
              .pr-icon-cancel-with-hover {
                display: flex;
              }
            }
          }
        }
      }
      .pr-screen-table-tr-no-items {
        background-color: #fbfbfb;
        padding: 6rem;
        text-align: center;
        border-radius: 0.5rem;
        .pr-screen-table-no-items-container {
          display: flex;
          align-items: center;
          flex-direction: column;
          justify-content: center;
          .pr-screen-table-no-items-txt {
            margin-top: 1rem;
            font-weight: 200;
            font-size: 18px;
            color: #aaaaaa;
          }
        }
      }
    }
  }
}

Why using JS click method don’t trigger WhatsApp web search name or number input

Title: WhatsApp Web Automation – Search Not Triggering Results


Issue:

I am automating WhatsApp Web to open a new chat, enter a phone number in the search bar, and load results. The number appears in the input field, but WhatsApp does not process it, and no results appear.

Code:

const number = "xxxxxx"; // Replace with your number

const newChatButton = document.querySelector('[data-icon="new-chat-outline"]');
newChatButton?.click();

const interval = setInterval(() => {
  const SEARCH_INPUT = document.querySelector('[aria-label="Search name or number"]');
  if (SEARCH_INPUT) {
    SEARCH_INPUT.focus();
    SEARCH_INPUT.click();
    SEARCH_INPUT.dispatchEvent(new InputEvent("input", { bubbles: true, data: number }));
    clearInterval(interval);
  }
}, 500);

Expected Behavior:

  1. Click New Chat.
  2. Insert the phone number.
  3. WhatsApp searches and loads results.

Observed Behavior:

  • The number appears but WhatsApp does not process it.
  • Manually typing works fine.

Question:

How can I trigger WhatsApp’s internal event listeners so it properly searches the number?

getVoices() in SpeechSynthesis returns different voices on same system

atm I am working on a project and want to add a TTS functionality using SpeechSynthesis. It works fine in MS Edge on Windows 11 but not in Firefox on the same machine.
I checked the documentation here: https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis/getVoices

Turns out that in the List Firefox only shows 5 voices whereas Edge shows many more, at least 40 I guess. That is obviously the reason why it won’t work in Firefox (French text is read with a German voice e.g.)

I always thought that the browsers use the voices that are installed on the system. How can it be that there is such a difference?

What can I do to get all availabe voices, like in Edge, also in Firefox?

Thank you

Loading Gutenberg Blocks with Ajax is stripping away scripts

I’m successfully loading a custom post type’s wp-content using ajax, but when it loads, a whole lot of the scripts are being stripped away.

fetch(ajax_object.ajax_url, {
    method: "POST",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
        action: "uipp_get_button_content",
        button_id: button_id,
    }),
})
.then(response => response.json())
.then(data => {
    if (data.success) {
        revealContentWrapper.innerHTML = data.data.content;
        console.log("Loaded content:", data.data.content);
        reinitializeScripts(revealContentWrapper);  // Ensures scripts run properly
    } else {
        revealContentWrapper.innerHTML = `<p>Error: ${data.message}</p>`;
    }
})

Note that I’ve set up this custom post to be editable in Gutenberg as I need it to be able to hold blocks.

If anyone has suggestions, would love to hear them!

Empty POST request body in Go controller, but in javascript it’s correct

I have administrative panel html/css/javascript code defined in admin.tpl file. It has this function:

    document.getElementById('userForm').addEventListener('submit', function(event) {
            event.preventDefault();
            const email = document.getElementById('email').value;
            const password = document.getElementById('password').value;
            const firstName = document.getElementById('firstName').value;
            const lastName = document.getElementById('lastName').value;
            const isAdmin = document.getElementById('isAdmin').value;
            const xsrfToken = getXSRFToken();

             fetch('/admin/users', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-XSRFToken': xsrfToken,
                    'Accept': 'application/json'
                },
                body: JSON.stringify({ Email: email, Password: password, First_name: firstName, Last_name: lastName, IsAdmin: isAdmin }),
            }).then(console.log(JSON.stringify({ Email: email, Password: password, First_name: firstName, Last_name: lastName, IsAdmin: isAdmin })))
            .then(response => {
                if(!response.ok){
                    return response.text().then(text => {
                        throw new Error(text)
                    });
                }
                return response.json();
            })
            .then(data => {
               fetchUsers()
              document.getElementById('userForm').reset()
            })
            .catch((error) => {
                console.error('Error while creating the user:', error);
                alert('Error while creating the user:', error);
            });
        });

But in my Go controller:

func (c *AdminController) AddUser() {
    var user models.User
    log.Log.Info(string(c.Ctx.Input.RequestBody))
    err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)

    if err != nil {
        log.Log.Critical("Failed to add new user: ", err.Error())
        c.Data["json"] = map[string]string{"status": "failed", "message": "Invalid JSON payload"}
        c.Ctx.ResponseWriter.WriteHeader(400) // Bad Request
        c.ServeJSON()
        return
    }

    err = user.SetPassword(user.Password)

    if err != nil {
        log.Log.Critical("Failed to hash user password: ", err.Error())
        c.Data["json"] = map[string]string{"status": "failed", "message": "Error hashing password: " + err.Error()}
        c.Ctx.ResponseWriter.WriteHeader(500) // Internal Server Error
        c.ServeJSON()
        return
    }

    o := orm.NewOrm()
    _, err = o.Insert(&user)

    if err != nil {
        log.Log.Critical("Failed to add new user: ", err.Error())
        c.Data["json"] = map[string]string{"status": "failed", "message": "Error creating user: " + err.Error()}
        c.Ctx.ResponseWriter.WriteHeader(500) // Internal Server Error
        c.ServeJSON()
        return
    }
    c.Data["json"] = map[string]string{"status": "success"}
    c.ServeJSON()
}

request body is completely ampty and i’m getting “Failed to add new user: %!(EXTRA string=unexpected end of JSON input)”.


My User model looks like this:

type User struct {
    Id               int       `orm:"pk;auto";json:"Id"`
    First_name       string    `json:"First_name"`
    Last_name        string    `json:"Last_name"`
    Email            string    `orm:"unique";json:"Email"`
    Phone_number     string    `json:"Phone_number"`
    Shipping_address string    `json:"Shipping_address"`
    Password         string    `orm:"size(128)";json:"Password"`
    Date_registered  time.Time `orm:"auto_now_add;type(datetime)";json:"Date_registered"`
    IsAdmin          bool      `orm:"default(false)";json:"IsAdmin`
}

I tried to set up cors in main.go to allow post, put, delete and options requests:

beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
        AllowAllOrigins:  true,
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Content-Type", "X-XSRFToken"},
        ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin"},
        AllowCredentials: true,
    }))

But still, request body is empty

Can not add option to select via javascript – shows a list of empty items

My goal is to add some items (option) to a select via javascript.

When I do this, the drop down list (select) has child elements (as can be seen using inspect element) but nothing is rendered on screen

This is easy to replicate but I can’t find out why this is the case.

My only guess is a race condition – I’m clearing the option elements by setting the parent’s innerHTML to null but I’m also convinced it can’t be a race condition because it’s synchronous.

JSFIDDLe

HTML

<select id="sel">
    <option>word</option>
</select>

Javascript

const ddl = document.getElementById("sel");

ddl.addEventListener("click", function(){

    ddl.innerHTML = null;
    const input = ["this", "that", "other"];
    for(let i = 0; i < input.length; i++){  
        const opt = document.createElement("option");
        opt.setAttribute("text", input[i]);  
        opt.setAttribute("value", i);
        ddl.appendChild(opt);
    }    
});

The result is

enter image description here

despite the HTML in the inspector panel showing

enter image description here

main.tsx framed inside of index.html

Overview

I’ve started building (and learning) with TailwindCSS v4 on Vite and working with HeroUI for the component library. The problem I’m experiencing came out of trying to define a theme for my page. I think here might be where the issues started to arise.

Problem

As you can see below, there seems to be a lot of padding/margins that are pushing the Main.tsxto the center. the Amber colour signifies the <div id='root'></div>. This is obviously not the desired effect since I want the main.tsx to take on the entire page.
Frontpage

Question

  • How do I set up my environment so that main.tsx is taking up the entire space on the page?
  • Why am I getting this margin/padding around main, I’ve never had this before?

Files

Here are the configs I’m working with. For the setup I followed the tailwind CSS v4 installation documentation. However it looks like the HeroUI

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Front Page</title>
  </head>
  <body>
    <div id="root" class="bg-amber-400"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

main.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { HeroUIProvider } from "@heroui/react";
import "./styles.css";
import App from "./App.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <HeroUIProvider>
      <main className="bg-black">
        <h1 className="text-white">Main Parent</h1>
        <App />
      </main>
    </HeroUIProvider>
  </StrictMode>
);

App.tsx

import "./App.css";

function App() {
  return (
    <div className="">
      <div className="text-white">
        <h1>This is the App.tsx</h1>
      </div>
    </div>
  );
}

export default App;

vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],
});

tailwind.config.js

const { heroui } = require("@heroui/react");

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  darkMode: "class",
  plugins: [
    heroui({
      addCommonColors: false, // override common colors (e.g. "blue", "green", "pink").
      defaultTheme: "light", // default theme from the themes object
      defaultExtendTheme: "light", // default theme to extend on custom themes
      layout: {}, // common layout tokens (applied to all themes)
      themes: {
        light: {
          layout: {
            spacing: {
              sm: "0.5rem",
              md: "1rem",
            },
          },
          colors: {
            background: "#ffffff",
            primary: "#6366F1",
            text: "#000000",
          },
        },
        dark: {
          extend: "dark",
          layout: {
            spacing: {
              sm: "0.5rem",
              md: "1rem",
            },
          },
          colors: {
            background: "#000000",
            primary: "#818cf8",
            text: "#ffffff",
          },
        },
      },
    }),
  ],
};

styles.css

@import "tailwindcss";

I’m hoping that I’ve just missed something, obvious but I’m hoping that someone can help me figure this out correctly.

Can I pass a segment of a url as a variable via embedded JS?

I have the following JS file embedded on multiple pages:

<script src="<?= $baseUrl?>/js/cms-functions.js"></script>

Within the embedded JS file, dynamic urls are created:

let albumURL = `${o.baseUrl}/category-1/` + encodeURI(albumTitle + "?album_id=" + albumId);

Is there a reliable method to pass the /category-1/ segment of the dynamic url as a variable via the script src tags?

How to Freeze a Flourish Studio Bar Chart Race Every 0.01s for High-Quality PDF Capture?

I created the following Flourish Studio bar chart race as an example for this topic:
https://flo.uri.sh/visualisation/19321054/embed

Now, I want to capture a sequence of PDF prints from this bar chart race, recording all movements and frames using the browser’s Print to PDF feature.

Why PDF Print?

I plan to later convert these PDF prints into a high-quality 4K video. The reason I prefer PDF prints over screen recording is that:

  • When zooming into the PDF, there is no blurriness, and the
    quality remains sharp.

  • Traditional screen recording (even with upscaling) results in
    blurry and low-quality frames when zoomed in.

Please do not suggest screen recording tools like OBS! They do not produce the high-quality frames I need.

Methods I Have Tried (But Failed)
I have attempted the following approaches, but they all have limitations in capturing and saving PDF print sequences efficiently:

  • Puppeteer (Node.js & Python)
  • Puppeteer with multiple browser contexts
  • Playwright (Python)
  • Built-in console commands (Chrome & Firefox)
  • Selenium WebDriver

All of these methods have a limitation: they can only capture one print every ~5 seconds, which is too slow.
I also tested MSS and DxCam, but they only record the screen, not the direct PDF print output from the browser. Screen recording is not as sharp as a PDF print.

My Current Idea
Since all other methods failed, I am looking for a way to freeze the bar chart race every 0.01 seconds and take a PDF print from each frozen frame.

My question:
Is there any tool, extension, script, or plugin that can freeze the Flourish Studio bar chart race every 0.01 seconds so that I can print each frame to a PDF?

Important Notes:

  • The built-in pause button of Flourish does not help, because
    when I pause, the bars automatically rearrange or shift, making
    it impossible to capture all frames accurately.

  • My PC is low-end and does not have a good GPU or capture
    card
    , so I cannot use high-performance screen recording tools.

My last hope is finding a tool that can:

  • Freeze the Flourish Studio animation every 0.01s

  • Allow me to print each frozen frame as a PDF

Does anyone know of a solution? Thanks in advance!

Nuxt 3 with GSAP – how to properly manage global timeline

I am currently building a website using Nuxt 3 and GSAP.
I want all the animations on the homepage to be connected, so I am using a Timeline for this purpose.

The flow of animations is as follows:

  • An entry animation (a curtain moving upwards),
  • Followed by revealing the content (e.g., the header),
  • And then, using ScrollTrigger, revealing the rest of the homepage content as the user scrolls.

In theory, this seems straightforward, but the homepage is composed of multiple components.
I am also using a layout to ensure that the navigation appears on every subpage.

Do you have any suggestions for a better approach to this? The idea I used was suggested by ChatGPT. But im not sure if its right.

Actually I have tried creating utility as solution but it seems more complex while im trying to consider multiple scenarios for example screen size and elements appearing conditionally based on that variable.

utils/globalTimeline.js

import gsap from "gsap";

const timeline = gsap.timeline({ paused: true });
const elements = [];
const scrollTimeline = gsap.timeline({ paused: true });
const scrollElements = [];

const addToTimeline = (element) => {
  if (Array.isArray(element)) {
    element.forEach((item) => {
      if (item.target) {
        elements.push(item.target);
      } else {
        elements.push(item);
      }
    });
  } else {
    elements.push(element);
  }
  if (elements.length >= 10) {
    timeline.fromTo(
      elements[0].target,
      {
        yPercent: 0,
      },
      {
        yPercent: -100,
        ease: "power4.inOut",
        duration: 1,
      }
    );

    timeline.play();
    elements.shift();
    timeline.fromTo(
      elements,
      {
        y: 16,
        opacity: 0,
        filter: "blur(8px)",
      },
      {
        y: 0,
        opacity: 1,
        filter: "blur(0px)",
        duration: 1,
        ease: "power4.inOut",
        stagger: 0.08,
      }
    );
  }
};

const addToScrollTimeline = (element) => {
  console.log(element[1].target.childNodes, "scroll timelien ");
  let whySection = [];
  let whySectionTrigger = element[0].target;

  whySection.push(...element[0].target.childNodes[0].childNodes);

  whySection.push(
    element[0].target.childNodes[1],
    element[0].target.childNodes[2]
  );

  scrollTimeline.fromTo(
    whySection,
    {
      y: 100,
      opacity: 0,
    },
    {
      y: 0,
      opacity: 1,
      ease: "power4.inOut",
      stagger: 1,
      scrollTrigger: {
        trigger: whySectionTrigger,
        scrub: 1,
        start: "top bottom+=20",
        end: "top center",
      },
    }
  );
};

export default {
  timeline,
  elements,
  addToTimeline,
  addToScrollTimeline,
};

pages/index.vue

const title = ref(null);
const subTitle = ref(null);
const buttons = ref(null);
const heroImages = ref(null);

const whyusRef = ref(null);
const processRef = ref(null);
const videoSectionRef = ref(null);
const realisationsSectionRef = ref(null);
const questionsRef = ref(null);

onMounted(() => {
  globalTimeline.addToTimeline([
    title.value,
    subTitle.value,
    buttons.value,
    heroImages.value,
  ]);
  globalTimeline.addToScrollTimeline([
    { target: whyusRef.value, title: "why-us" },
    { target: processRef.value, title: "process" },
    { target: videoSectionRef.value, title: "video-section" },
    { target: realisationsSectionRef.value, title: "realisations-section" },
    { target: questionsRef.value, title: "questions-section" },
  ]);
});
onUnmounted(() => {
  globalTimeline.timeline.kill();
  globalTimeline.scrollTimeline.kill();
});

Status text ignored when HTTP status is 500

In Deno when I send a response with a 500 status, and some custom statusText, the client always receives statusText as “Internal server error”, not my custom text.

return new Response(body, { status: 500, statusText: "my error message" });

Is this normal behavior?

What is the standard way of sending a proper error message to the user with 500 ?

Leetcode API for specific submission details using authentication

I am working on creating an API using express.js, I want to get time taken, space used, code and all relevant details for a particular submission using its submission id.

I know leetcode doesn’t allow this for an anonymous user that’s why I am attaching my cookie but have failed in my every attempt in creating a http request from scratch by attaching headers and cookies.

I have tried directly making a GET request at this endpoint https://leetcode.com/submissions/detail/{submission_id}/, but got 403 forbidden error.

Tried using Leetcode-query api but it didn’t work either but authentication was working with this.

Tried various other unofficial leetcode APIs but many of them don’t offer authenticated request functionality.Similar to the image, I tried get, post with different combinations of headers like referer, origin, cookie, xcsrf etc but nothing worked

How to Restrict Firebase Realtime Database Access to Specific Query Parameters?

I have a domain whitelist system to verify whether a site’s domain is included in my whitelist. If a domain is on the list, it means the theme is verified and purchased. Otherwise, the code should not work.

My whitelist is stored in a Firebase Realtime Database JSON file at:
https://sitelicenses-default-rtdb.firebaseio.com/domains.json

Problem:
Currently, if someone accesses this URL directly https://sitelicenses-default-rtdb.firebaseio.com/domains.json, they can see the entire list of whitelisted domains. Instead, I want to restrict access so that:

If a request includes a specific domain as a parameter (e.g., https://sitelicenses-default-rtdb.firebaseio.com/domains.json/www.prothomalo-tenolent.blogspot.com), it should return only that domain if it’s in the list.
If accessed without a valid domain parameter (e.g., https://sitelicenses-default-rtdb.firebaseio.com/domains.json), nothing should be shown.

My JSON Data Structure in Firebase:

[
  "www.eatingfact-tenolent.blogspot.com",
  "www.prothomalo-tenolent.blogspot.com"
]

I am using Firebase Realtime Database, and my firebase rules is now:

{
  "rules": {
      ".read": true,
      ".write": true
    }
}

Is there a way to enforce this restriction using Firebase rules or any other method? Is it possible to achieve this behavior securely? Any guidance would be appreciated!

I tried to Restrict Firebase Realtime Database Access to Specific Query Parameters

js submit button continuously clicking on button

I have form on my website with JS. When submitted, seems submit button continuously clicking, without giving success message, even though form details successfully received. Not sure what wrong I am doing. New to JS. Added here JS I am using with form. After “Submit” press, its shows ‘Sending …’ continuously, and not directing to “Success’ action or Error action. However, the form details received correctly posit Submit.

Original link is

https://swotconsulting.com.au/try6/

JS coped in box below:

$(function()
{
    function after_form_submitted(data) 
    {
        if($.trim(data.result) == 'success')
        {
            $('form#reused_form').hide();
            $('#success_message').show();
            $('#error_message').hide();
        }
        else
        {
            $('#error_message').append('<ul></ul>');

            jQuery.each(data.errors,function(key,val)
            {
                $('#error_message ul').append('<li>'+key+':'+val+'</li>');
            });
            $('#success_message').hide();
            $('#error_message').show();

            //reverse the response on the button
            $('button[type="button"]', $form).each(function()
            {
                $btn = $(this);
                label = $btn.prop('orig_label');
                if(label)
                {
                    $btn.prop('type','submit' ); 
                    $btn.text(label);
                    $btn.prop('orig_label','');
                }
            });
            
        }//else
    }

    $('#reused_form').submit(function(e)
      {
        e.preventDefault();

        $form = $(this);
        //show some response on the button
        $('button[type="submit"]', $form).each(function()
        {
            $btn = $(this);
            $btn.prop('type','button' ); 
            $btn.prop('orig_label',$btn.text());
            $btn.text('Sending ...');
        });
        

                    var formdata = new FormData(this);
            $.ajax({
                type: "POST",
                url: 'handler.php',
                data: formdata,
                success: after_form_submitted,
                dataType: 'json' ,
                processData: false,
                contentType: false,
                cache: false        
            });
        
      });   
});

Finding All Instances Of A Specific Word Within A Multidimensional Array

I have here a nested array:

const roster = [["Ryu","RyuNormal"], ["Ryu as Ken","RyuKen"], ["Ryu as Akuma","RyuAkuma"], ["Chun-Li","ChunLi"],["Zangief"]]

And a for-loop that will produce options for a dropdown menu:

for ([char, link] of roster) {
    options += `n <OPTION class="show" data-toggle="${link ?? char}">${char}</OPTION>`;
}

Each bracket in the nested array consists of the character’s name before the comma, and a toggle link ref after the comma. If the bracket has only one entry and not two (see Zangief), it is to be assumed the character’s name doubles as a toggle link, hence the double question marks.

Suppose you are building a chart that compares special move functions between two characters. If you were on Chun-Li or Zangief’s respective web pages, the dropdown menu would gladly show you Ryu’s moves in addition to his Ken and Akuma forms (for those not in the know, in the first Marvel vs. Capcom game, Ryu had the ability to switch styles), so their OPTION tags would be included here. However, if you were on Ryu’s page, the only OPTION tags you would be able to see are Chun-Li’s and Zangief’s, which is the intended result.

console.log(result) // [["Chun-Li","ChunLi"],["Zangief"]] if on Ryu page
console.log(result) // [["Ryu","RyuNormal"], ["Ryu as Ken","RyuKen"], ["Ryu as Akuma","RyuAkuma"], ["Zangief"]] if on Chun-Li page

I have tried the findIndex and filter functions, but neither of them worked. When I tried the filter function, the console log kept bringing up the entire array no matter if I used the !== or the includes() parameter. The findIndex function kept giving me a -1 (no match) whenever I tried looking up all entries that had the word “Ryu.” As this was a nested array, using the brackets in my field of search somewhat alleviated the situation, but there were some mishaps, like removing Chun-Li and Zangief because they were unique results but only removing one instance of Ryu when it should have removed all three instances.

Codes used:

var index = roster.filter(x => x !== "Ryu");
// Filter matching string

var index = roster.filter(x => x !== roster.includes("Ryu"));
// Filter with includes

var index = roster.findIndex(x => x == "Ryu");
if (index >= 0)
    data.splice(index,);
// Find index and splice

How do I revise the code so that the filter and/or findIndex function properly finds all instances of a word in the first half of any bracketed array? Array splicing is optional.