Animate text while using background-clip with text

I’m trying to animate text and change the color in the middle of the screen, like this
enter image description here

HTML

<div class="text-mask">
    <div class="scrolling-text">MASKED TEXT</div>
</div>

CSS

body {
  background-color: #000;
}
.text-mask {
  font-size: 100px;
  font-weight: bold;
  background: linear-gradient(to right, orange 50%, white 50%);
  background-clip: text;
  color: transparent;
  position: relative;
  height: 120px;
}

.scrolling-text {
  /* transform: translateX(10px); */
  /* position: relative */
  
} 

DEMO

As soon as I apply the styling of .scrolling-text the text is gone. The idea I had was to change the value of the transform in order to get the walking/animated text.

Is there a way I can achieve this?

Firebase Cloud Storage error xhr2 : Unsupported send() data [object Blob]

I use Firebase Cloud Storage with React-Native and I’m trying to upload a byte array in using test function (with Jest), but when I execute uploadBytes I’ve got an error:

Unsupported send() data [object Blob]

My code:

// Test upload file
test('Upload d un fichier', async () => {
   const storage = getStorage();
   const storageRef = ref(storage, 'some-child');
   
   const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]);
   uploadBytes(storageRef, bytes).then((snapshot) => {
     console.log('Uploaded an array!');
   });
})

In details:

C:UsersFLORIANDDocumentsHumanityHumanitynode_modulesxhr2libxhr2.js:815
throw new Error(`Unsupported send() data ${data}`);
          ^

Error: Unsupported send() data [object Blob]
at XMLHttpRequestUpload._setData (C:UsersFLORIANDDocumentsHumanityHumanitynode_modulesxhr2libxhr2.js:815:17)
at XMLHttpRequest._sendHttp (C:UsersFLORIANDDocumentsHumanityHumanitynode_modulesxhr2libxhr2.js:304:23)
at XMLHttpRequest.send (C:UsersFLORIANDDocumentsHumanityHumanitynode_modulesxhr2libxhr2.js:192:20)
at XhrTextConnection.send (C:UsersFLORIANDDocumentsHumanityHumanitynode_modules@firebasestoragesrcimplementationrequests.ts:469:9)
at doTheRequest (C:UsersFLORIANDDocumentsHumanityHumanitynode_modules@firebasestoragedistindex.esm2017.js:639:20)
at Timeout._onTimeout (C:UsersFLORIANDDocumentsHumanityHumanitynode_modules@firebasestoragedistindex.esm2017.js:394:7)
at listOnTimeout (node:internal/timers:573:17)
at processTimers (node:internal/timers:514:7)

I use xhr2 and I place global.XMLHttpRequest = require("xhr2"); in the head of my file.

Do you have any idea? I’m trying to test with jest my function to upload a byte array. I expect to retrieve my file on Firebase storage.

keep mouse focus / don’t scroll page when page content changes by selecting filter option [closed]

I have the following page filter / mouse focus problem:

Focus problem / page jump

Image 1 shows the page (red square) and the screen viewport (dark horizontal rectangle) what the user sees. The mouse hovers over a filter option (yellow) on the lower part of the visible screen. The first 4 filter options on the left and the first filterable content (green) on the right are not in the viewport and therefore not visible for the user because he has scrolled down. Below the filterable content is other content (orange), not affected by the filter options on the left.

Now, the user clicks on one or more filter options (yellow).

Image 2 shows the result:
The now available amount of filter options can change based on the selected filters. Furthermore the content also gets less or more because with every filter option checked, you narrow down/expand the available content (green) so that only relevant content, which respects the filter options, are shown.
The mouse arrow stays on the previous position where the user has clicked the last filter option.

Because of the changed filterable content, the page is shorter (when you add filters) and therefore it appears as if the mouse jumps to the bottom of the page, in this case the other content (orange) comes into focus and is shown below the mouse cursor. It’s also possible that the user has to scroll up to see any results at all, if for example the footer comes into the viewport.

Also the other way is possible. If you remove filter options again, the page could get longer again with more content available which would lead to a jump to to the top.

Image 3 shows the desired result:
The clicked filter option should stay in focus below the mouse cursor. The filtered page will still be shorter/longer but the user shouldn’t scroll up/down to see the content.

I thought about to jump to the top of the list everytime the user clicks on a filter option but that seems also not a good user experience because the user has to scroll down/up, everytime.

Not sure what’s the best solution here but keeping the focus on the just selected filter option seems to be a good option, or?

Is this possible to achive this with CSS only (prevent scrolling somehow) or with some JavaScript?

Thank you

Why is useLayoutEffect used in useClickAway React Hook?

Here’s the source for useClickAway hook:

export function useClickAway(cb) {
  const ref = React.useRef(null);
  const refCb = React.useRef(cb);

  React.useLayoutEffect(() => {
    refCb.current = cb;
  });

  React.useEffect(() => {
    const handler = (e) => {
      const element = ref.current;
      if (element && !element.contains(e.target)) {
        refCb.current(e);
      }
    };

    document.addEventListener("mousedown", handler);
    document.addEventListener("touchstart", handler);

    return () => {
      document.removeEventListener("mousedown", handler);
      document.removeEventListener("touchstart", handler);
    };
  }, []);

  return ref;
}

I am particularly interested in the usage of useLayoutEffect here. The documentation of useLayoutEffect suggests it should only ever be used when working with layout and I don’t see anything of that sort happening.

Why not just use the following which updates the callback ref to the latest cb whenever it changes?

React.useEffect(() => {
  refCb.current = cb;
}, [cb]);

My assumption is useLayoutEffect synchronously runs before the browser repaints so updating refCb there means a much more guaranteed and correct callback on the immediate click after browser repaint whereas a useEffect would probably be slower being asynchronous and within that slowness, a click could still execute the previous callback.

Is my understanding correct or is there a deeper reason for why useLayoutEffect is being used here?

HTML services won’t work as they are in a library

I am very new to google apps script. I have a container bound script that works so I wanted to share it as a library. The problem is that after sharing it as a library, it doesn’t function as it should be. Checked the console and I am getting this error
Uncaught TypeError: google.script.run.withSuccessHandler(...).processForm is not a function

Library

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(data) {
  // Get the active spreadsheet and the sheet named 'Sheet1'
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName('Sheet1');
  
  // Get the header row (first row) from the sheet
  const headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  
  // Find the column that has "acreage" in the header
  let acreageColumnIndex = -1; // Default value if the header is not found
  for (let i = 0; i < headerRow.length; i++) {
    if (headerRow[i].toLowerCase().includes("acreage")) {
      acreageColumnIndex = i + 1; // Convert to 1-based index for Google Sheets
      break; // Stop searching once we find the correct column
    }
  }
  
  // If no "acreage" header is found, throw an error
  if (acreageColumnIndex === -1) {
    throw new Error("Header with the word 'acreage' not found.");
  }

  // Get the submitted data from the form
  const minRanges = data.minRange; // Array of min range values
  const maxRanges = data.maxRange; // Array of max range values
  const values = data.value; // Array of values to be inputted

  // Get all the values in the "acreage" column, starting from the second row
  const numRows = sheet.getLastRow() - 1; // Total number of rows, excluding the header row
  const acreageValues = sheet.getRange(2, acreageColumnIndex, numRows, 1).getValues(); // Get all values in the "acreage" column

  // Loop through each range that the user submitted in the form
  for (let i = 0; i < minRanges.length; i++) {
    const minRange = parseFloat(minRanges[i]); // Minimum range value
    const maxRange = parseFloat(maxRanges[i]); // Maximum range value
    const inputValue = values[i]; // Value to be inserted if the range matches

    // Check if both minRange and maxRange are valid numbers
    if (!isNaN(minRange) && !isNaN(maxRange)) {
      // Loop through all the "acreage" values and check if they fall within the range
      for (let j = 0; j < acreageValues.length; j++) {
        const acreage = parseFloat(acreageValues[j][0]); // Get the acreage value from the current row

        // If the acreage value is within the range (inclusive), set the value in the cell next to it
        if (!isNaN(acreage) && acreage >= minRange && acreage <= maxRange) {
          sheet.getRange(j + 2, acreageColumnIndex + 1).setValue(inputValue); // j + 2 because rows start from 2 (excluding header)
        }
      }
    }
  }
}



function showForm() {
  const html = HtmlService.createHtmlOutputFromFile('Index')
    .setWidth(100) // Adjust the width for the sidebar
    .setHeight(600); // You can set a height, but it’s generally not needed for a sidebar
  SpreadsheetApp.getUi().showSidebar(html); // Show the sidebar instead of modal dialog
}



function onOpen(e){
  const ui = SpreadsheetApp.getUi();
  ui.createMenu("Custom Menu")
  .addItem("Open Form", 'showForm')
  .addToUi();
}


Index.HTML

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body {
        font-family: Arial, sans-serif;
        background-color: #f9f9f9;
        margin: 0;
        padding: 5px;
      }
      h2 {
        text-align: center;
        color: #333;
      }
      .form-container {
        max-width: 600px;
        margin: 0 auto;
        background: #fff;
        padding: 5px;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        border-radius: 5px;
      }
      table {
        width: 100%;
        border-collapse: collapse;
        margin-bottom: 20px;
      }
      th, td {
        border: 1px solid #ddd;
        padding: 5px;
        text-align: center;
      }
      th {
        background-color: #f0f0f0;
        color: #333;
      }
      input[type="text"] {
        width: 100%;
        padding: 5px;
        box-sizing: border-box;
        border: 1px solid #ccc;
        border-radius: 5px;
      }
      input[type="button"] {
        background-color: #4CAF50;
        color: white;
        border: none;
        padding: 10px 20px;
        font-size: 16px;
        cursor: pointer;
        border-radius: 5px;
      }
      input[type="button"]:hover {
        background-color: #45a049;
      }
    </style>
  </head>
  <body>
    <div class="form-container">
      <h2>Input Ranges Form</h2>
      <form id="rangeForm">
        <table>
          <thead>
            <tr>
              <th>Min Range</th>
              <th>Max Range</th>
              <th>Value</th>
            </tr>
          </thead>
          <tbody>
            <!-- Creating five rows for input -->
            <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
            <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
            <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
            <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
            <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
             <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
             <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
             <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
             <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
             <tr>
              <td><input type="text" name="minRange" required></td>
              <td><input type="text" name="maxRange" required></td>
              <td><input type="text" name="value" required></td>
            </tr>
          </tbody>
        </table>
        <input type="button" value="Submit" onclick="handleSubmit()">
      </form>
    </div>

    <script>
      function handleSubmit() {
        const formData = new FormData(document.getElementById('rangeForm'));
        const data = {};
        
        // Group form data by name
        formData.forEach((value, key) => {
          if (!data[key]) {
            data[key] = [];
          }
          data[key].push(value);
        });

        // Pass the grouped data to Google Apps Script function
        google.script.run
          .withSuccessHandler(() => {
            alert("Values submitted successfully!");
          })
          .processForm(data);
      }
    </script>
  </body>
</html>

Container bound

Code.gs

function onOpen(e) {
 test()
}


function test(e){
  var menu = SpreadsheetApp.getUi().createMenu('Custom Menu')
  menu.addItem("Open Form", 'Automation.showForm')
  menu.addToUi();
}

I tried searching on what was the cause and I read that HTML services on a library has an extra step but I couldn’t wrap my head around it.

How do I make a slideshow popup with next/prev arrows within the image?

I want to make a popup with a slideshow but I have a problem, I can’t get the naviagtion arrows be placed inside the image container, so when the website is resized they stay in the same place.

I want to move the navigation arrows here (Purple indicating where I want them to be)

Here’s what I’d like it to look like

Website showing what I have right now

HTML Fragment:

<div id="popup" class="popup hidden">
  <div class="popup-content">
    <span id="closePopup" class="close">&times;</span>
    <button id="prevImage" class="nav-button">&lt;</button>
    <img id="popupImage" src="" alt="Project Image">
    <button id="nextImage" class="nav-button">&gt;</button>
    <div class="popup-text">
      <p id="popupName"></p>
      <p id="popupDescription"></p>
    </div>
  </div>
</div>

CSS Fragment:

/* Popup */
.popup {
  display: none;
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  overflow: auto;
  background-color: rgba(0,0,0,0.5);
}

.popup-content {
  background-color: #494949;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 20px;
  padding-top: 30px;
  padding-bottom: 30px;
  padding-left: 30px;
  border-radius: 20px;
  width: 70vw;
  max-width: 1600px;
  height: auto;
  max-height: 1000px;
  text-align: left;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  position: relative;
}

#popupImage {
  width: 55%; 
  height: auto;
  border-radius: 20px;
  margin-right: 8%;
  position: relative;
}

#popupName {
  font-size: 3vh;
  font-weight: bold;
  margin-bottom: 10px;
}

#popupDescription {
  font-size: 2vh;
}


/* Navigation Buttons */
.nav-button {
  background-color: transparent;
  border: none;
  color: #fff;
  font-size: 2em;
  cursor: pointer;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
}

#prevImage {
  left: 1.5%;
}

#nextImage {
  right: 1.5%;
}

How to pass HTTP headers to WCF service calls from AjaxWebService(Ajax-enabled) in existing project?

We have an ASP.NET web application that consumes WCF services. The current system structure is as follows:

In AjaxWebService.svc.cs:
 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
 [ServiceContract(Namespace = "", SessionMode = SessionMode.Allowed)]
 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

private Service m_Service;
private Service Service
{
    get
    {
        if (m_Service == null)
        {
            m_Service = new Service();
        }
        return m_Service;
    }
}

 [OperationContract]
internal Users[] getUsers()
{
    return this.Service.getUsers();
}
 [OperationContract]
internal products[] getproducts()
{
    return this.Service.getproducts();
}

In Service.cs:

private IServiceClient m_ServiceGateway;
private IServiceClient ServiceGateway
{
    get
    {
        if (m_ServiceGateway == null)
        {
            m_ServiceGateway = new IServiceClient();
        }
        return m_ServiceGateway;
    }
}

internal Users[] getUsers()
{
    return this.ServiceGateway.getUsers(this.RoleId, this.Token);
}

internal products[] getproducts()
{
    return this.ServiceGateway.getproducts(this.RoleId, this.Token);
}

In IServiceGateway.cs:
[OperationContract]
List<getUsers> getUsers(int RoleId, string Token);
[OperationContract]
List<products> getProducts(int RoleId, string Token);

Problem:
I want to pass custom HTTP headers (like User-Agent and Accept) to WCF service calls without changing the existing method signatures (e.g., return this.ServiceGateway.getUsers(this.RoleId, this.Token); and return this.ServiceGateway.getproducts(this.RoleId, this.Token);).

I’ve tried to initialize and pass headers within the ServiceGateway property like this:

private IServiceClient m_ServiceGateway;
private IServiceClient ServiceGateway
{
    get
    {
        if (m_ServiceGateway == null)
        {
            m_ServiceGateway = new IServiceClient();
            using (new OperationContextScope(m_ServiceGateway.InnerChannel))
            {
                // Set HTTP headers
                HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
                httpRequestProperty.Headers["User-Agent"] = "mrt/1.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)";
                httpRequestProperty.Headers["Accept"] = "application/json";

                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
            }
        }
        return m_ServiceGateway;
    }
}

However, the headers are not being passed correctly to the WCF service.

Question:
Is there a way to pass these headers to the WCF service without changing the existing method calls like return this.ServiceGateway.getUsers(this.RoleId, this.Token);? How can I apply custom headers centrally to all WCF service calls without modifying every API?

Is there a way to Get list of approved reviews for a pull request in github action

Am working on a usecase to get the list of approvals on a pr for a github action using any end point API.

Tried below code which didn’t work

const reviews = await github.rest.pulls.reviews({ owner, repo, pull_number: pr.number }); const approvedReview = reviews.data.find(review => review.state === "APPROVED");

Also refered here https://docs.github.com/en/rest/pulls/reviews?apiVersion=2022-11-28 but didnt work.

Why error in the “synchronous” part of promise/async function is handled differently

Compare these 2 snippets:

try {
    console.log('start')
    const test = (() => {
        console.log('inside')
        const a = null;
        a.c = 1
    })()
    console.log('end')
} catch (error) {
    console.log('nvmnghia');
    console.error(error);
}

// prints:
// start
// inside
// nvmnghia
// TypeError: set props of null
try {
    console.log('start')
    const test = (async () => { // Note the async without await
        console.log('inside')
        const a = null;
        a.c = 1
    })()
    console.log('end')
} catch (error) {
    console.log('nvmnghia');
    console.error(error);
}

// prints:
// start
// inside
// end
// undefined, due to normal return
// Uncaught (in promise) TypeError: ...
//   and no nvmnghia, which means the error wasn't caught indeed

Why does it behave this way? I’m surpise cuz my mental model for async/promise has always been that the part before the first await is run synchronously i.e. NOT scheduled in a microtask.

Twitter Oauth 2.0 permission scope configuration in auth.js or next-auth only getting users.read tweet.read offline.access scope

What ever scope i Give i only get users.read tweet.read offline.access scope like this url from twitter https://x.com/i/oauth2/authorize?scope=users.read+tweet.read+offline.access.

This is my code I have check https://github.com/nextauthjs/next-auth/discussions/4251 and try to implement according to it but it doesn’t work. whatever i do only three scope is available.

import TwitterProvider from 'next-auth/providers/twitter';
export const config = {
  debug: true,
  pages: {
    signIn: '/sign-up',
  },
  providers: [
    TwitterProvider({
      //@ts-ignore
      version: '2.0', // opt-in to Twitter OAuth 2.0
      authorization: {
        url: 'https://x.com/i/oauth2/authorize',
        params: {
          scope: 'users.read tweet.read tweet.write offline.access',
        },
      profile(profile) {
        return {
          id: profile.data.id,
          name: profile.data.name,
          image: profile.data.profile_image_url,
          email: profile.data.email,
          username: profile.data.username,
        };
      },
    }),
  ],

How do I trigger an external vanilla JS function from within a React component?

I am building an application in React and placing it on a page on my site.

I have a 3rd party chat widget/plugin on my site and would like it to be activated when a particular button is clicked from within my React component. In vanilla JS this is usually done with the following function:

LiveChat.openChatWindow();

However, I cannot figure out now to call this function from within my react component.

I’ve tried

  const launchChat = () => {
    LiveChat.openChatWindow();
  }
// Other code

  <a class="btn chat-btn" onClick={launchChat}>Chat to us</a>

But the application wont compile because, of course, LiveChat is not defined (on my local machine).

I cannot seem to launch the chat by detecting a click from outside the component either, because, of course, the button is created by React, and not in the DOM.

$(function(){
  $(".chat-btn").click(function(){
    LiveChat.openChatWindow();
  });
});

Would anyone know how I should be going about this?

Images failing to visually display in JS

I am making a game with JS as the main language, I am experiencing no errors or issues on F12 console, but my images don’t seem to load visually, when I open the F12 menu, under sources they are there next to my JS code and html index file. Code is further below, I’m using express, node.js, tensorflow.js, JS, and html. (local) Apologies for the large pieces of code, the image loading is spread across the whole code.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Game</title>
    <style>
        canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
  <canvas id="gameCanvas" width="640" height="480"></canvas>
  <script src="script.js"></script>
  </body>
</html>

JS

    const canvas = document.getElementById('gameCanvas');

const ctx = canvas.getContext('2d');

const canvasWidth = 640;
const canvasHeight = 480;
canvas.width = canvasWidth;
canvas.height = canvasHeight;

// Load images
const playerImage = new Image();
playerImage.src = 'player.png';
const projectileImage = new Image();
projectileImage.src = 'projectile.png';
const platformImage = new Image();
platformImage.src = 'platform.png'; 


let platforms = [154, 204];

function checkCollisions() {
  detectPlayerCollisions(player1, player2);

  projectiles.forEach(projectile => {
    if (projectile.isPlayer1) {
      if (isColliding(projectile, player2)) {
        player2.takeDamage(10);  // Example damage; adjust as needed
        projectiles.splice(projectiles.indexOf(projectile), 1);
      }
    } else {
      if (isColliding(projectile, player1)) {Stop
        player1.takeDamage(10);Stop
        projectiles.splice(projectiles.indexOf(projectile), 1);
      }
    }
  });
}

function isColliding(rect1, rect2) {
    return (
        rect1.x < rect2.x + rect2.width &&
        rect1.x + rect1.width > rect2.x &&
        rect1.y < rect2.y + rect2.height &&
        rect1.y + rect1.height > rect2.y
    );
}

// Detect collision between two players
function detectPlayerCollisions(playerA, playerB) {
    if (isColliding(playerA.hitbox, playerB.hitbox)) {
        playerA.takeDamage(5);  // Example damage; adjust as needed
        playerB.takeDamage(5);  // Example damage; adjust as needed
    }
}

class Player {
  constructor(x, y, isPlayer1) {
    this.x = x;
    this.y = y;
    this.width = 32;
    this.height = 32;
    this.speed = 3;
    this.gravity = 0.5;
    this.velocity = { x: 0, y: 0 };
    this.hitbox = {
      x: this.x,
      y: this.y,
      width: this.width,
      height: this.height,
    };
    this.isPlayer1 = isPlayer1;
    this.isJumping = false;
    this.jumpForce = 8;
    this.health = 100;
  }

  takeDamage(amount) {
    this.health -= amount;
  }

  moveLeft() {
    this.velocity.x = -this.speed;
  }

  moveRight() {
    this.velocity.x = this.speed;
  }

  stop() {
    this.velocity.x = 0;
  }

  jump() {
    if (!this.isJumping) {
      this.isJumping = true;
      this.velocity.y = -this.jumpForce;
    }
  }

  shoot() {
    const projectile = new Projectile(this.x + this.width / 2, this.y, this.isPlayer1);
    projectiles.push(projectile);
  }

  update(deltaTime) {
    this.velocity.y += this.gravity * deltaTime;
    this.x += this.velocity.x * deltaTime;
    this.y += this.velocity.y * deltaTime;

    // Update hitbox position
    this.hitbox.x = this.x;
    this.hitbox.y = this.y;
  }

  draw() {
    if (playerImage.complete) {
      ctx.drawImage(playerImage, this.x, this.y, this.width, this.height);
    } else {
      ctx.fillRect(this.x, this.y, this.width, this.height); // Draw rectangle as placeholder
    }
  }
}

class Projectile {
  constructor(x, y, isPlayer1) {
    this.x = x;
    this.y = y;
    this.width = 8;
    this.height = 8;
    this.speed = 5;
    this.isPlayer1 = isPlayer1;
  }

  update() {
    if (this.isPlayer1) {
      this.x += this.speed;
    } else {
      this.x -= this.speed;
    }
  }

  draw() {
    if (projectileImage.complete) {
      ctx.drawImage(projectileImage, this.x, this.y, this.width, this.height);
    } else {
      ctx.fillRect(this.x, this.y, this.width, this.height); // Draw rectangle as placeholder
    }
  }
}

// Create player and platform objects
const player1 = new Player(32, canvas.height - 32, true);
const player2 = new Player(canvas.width - 64, canvas.height - 32, false);
let projectiles = [];

// Input handling
let keysPressed = [];

window.addEventListener('keydown', (event) => {
  keysPressed.push(event.key);
});

window.addEventListener('keyup', (event) => {
  const index = keysPressed.indexOf(event.key);
  if (index !== -1) {
    keysPressed.splice(index, 1);
  }
});

// Function to update game state and send to API
function updateGameState() {
  fetch('http://localhost:3000/update-players', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      players: [
        { id: 1, x: player1.x, y: player1.y, health: player1.health },
        { id: 2, x: player2.x, y: player2.y, health: player2.health }
      ]
    })
  });
}

// Function to sync projectiles with API
function syncProjectiles() {
  fetch('http://localhost:3000/add-projectiles', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      projectiles: projectiles.map(p => ({ x: p.x, y: p.y, isPlayer1: p.isPlayer1 }))
    })
  });
}

function gameLoop(currentTime) {
  const deltaTime = (currentTime - previousTime) / 10000;
  previousTime = currentTime;

  ctx.clearRect(0, 0, canvasWidth, canvasHeight);

  // Player 1 controls
  if (keysPressed.includes('w')) {
    player1.jump();
  }
  if (keysPressed.includes('a')) {
    player1.moveLeft();
  }
  if (keysPressed.includes('d')) {
    player1.moveRight();
  }
  if (keysPressed.includes(' ')) {
    player1.shoot();
  }

  // Update proj and players
  player1.update(deltaTime);
  player1.draw();
  player2.update(deltaTime);
  player2.draw();

  for (let i = 0; i < projectiles.length; i++) {
    projectiles[i].update();
    projectiles[i].draw();
  }

  checkCollisions();

  // Update game for API
  updateGameState();
  syncProjectiles();

  requestAnimationFrame(gameLoop);
}

let previousTime = 0;

requestAnimationFrame(gameLoop);

I tried searching, reformatting and even rewriting the code entirely, I’m tired of looking and want a hand, any takers?

Getting the “horizontality”of a bezier curve at a specific point

Apologies in advance if this is more of a math question and not a JavaScript question.

enter image description here

As illustrated above, I have a bezier segment (in lime) that I’ve constructed using the library bezier-js. For this demo, I am sampling points on the curve, as represented by the black dots on the lime curve.

For each sampled point, I can offset it by a specific distance using bezier-js. For instance, the black dotted line you see is constructed using offset the points from the lime curve by 20px.

What I want to accomplish:
Depending on how “horizontal” the tangent of each sampled point is, I want to be able to offset it by a different amount. More specifically, I want to be able to offset the horizontal segments less (min. 20px) and the more vertical segments more (max. 50px). A bit like how a letter O looks in a rendered font.

enter image description here

As you can see, the red dotted line is my attempt at achieving this effect. Somehow, it takes a swirl when it gets closer to the more vertical segments. Here’s how I calculate the horizontality:

// ANGLE: the tangential angle at the point
// map: mapping function

const getAngleHorizontality = angle => {
  const TWO_PI = 2 * Math.PI
  let normalizedAngle = angle % TWO_PI
  if (normalizedAngle < 0)
    normalizedAngle += TWO_PI

  const proximityToZero = delta(normalizedAngle, 0)
  const proximityToPi = delta(normalizedAngle, Math.PI)

  return Math.min(proximityToZero, proximityToPi) / Math.PI * 2
}

const horizontality = getAngleHorizontality(ANGLE)
const offsetDistance = map(horizontality, 0, 1, 20, 50)

The blue dotted line is my simulated result: it is closer to the original curve at the horizontal sections, and gets farther gradually as it becomes more vertical.
Can anyone help me with this problem?

How to retrieve data from JS libp2p/kad-dht Distributed Hash Table?

I hope you are doing well.
I created a Kademlia hash table to store content across nodes on the network.
When using the get method, I am getting an AsyncIterable<QueryEvent>.

Can someone provide an example of how to extract data from the QueryEvent?

This is what I thought could work :

const extractDHTValue = async (res) => {
    let finalValue = null;

    for await (const event of res) {
        if (event.name === 'VALUE' && event.record) {
            // Extract the value from the record
            const value = uint8ArrayToString(event.record.value);
            console.log('DHT Record Value:', value);
            finalValue = value;
        }
    }

    if (finalValue === null) {
        console.log('No value found in the DHT query response.');
    }

    return finalValue;
};

I am unfortunately getting no value.

By the way, this is the way I am saving data on DHT :

import { createLibp2p } from 'libp2p';
import { tcp } from '@libp2p/tcp';
import { noise } from '@chainsafe/libp2p-noise';
import { stdinToStream, streamToConsole } from './stream.js';
import { yamux } from '@chainsafe/libp2p-yamux';
import { pipe } from 'it-pipe';
import { kadDHT } from '@libp2p/kad-dht';
import { identify, identifyPush } from '@libp2p/identify';
import { fromString as uint8fromString } from 'uint8arrays/from-string';

const start_server = async () => {
    const node = await createLibp2p({
        addresses: {
            listen: ["/ip4/0.0.0.0/tcp/10333"]
        },
        transports: [
            tcp()
        ],
        streamMuxers: [
            yamux()
        ],
        connectionEncrypters: [
            noise()
        ],
        contentRouters: [
            kadDHT()
        ],
        services: {
            identify: identify(),
            dht: kadDHT()
        }
    });

    // await node.start();
    node.addEventListener('peer:connect', (evt) => {
        console.log("New peer connected", evt);
        // const remotePeer = evt.details;
        // console.log('Connection established to:', remotePeer.toString());
    })

    await node.handle('/chat/1.0.0', async ({ stream }) => {
        console.log("New stream of chat protocol");
        stdinToStream(stream);
        streamToConsole(stream);

        // SAVING DUMB DATA HERE 
        await node.services.dht.put(uint8fromString('hello'), uint8fromString('world'));
        // CAN I READ THE DATA HERE TOO ?

        
    });

    node.getMultiaddrs().forEach((ma) => {
        console.log('Listening on:', ma.toString());
    });

    console.log('Node started!');
}

start_server();