Getting 401 on reddit auth

const encodedHeader = Buffer.from(
      `${process.env.REDDIT_CLIENT_ID}:${process.env.REDDIT_CLIENT_SECRET}`,
    ).toString('base64');
    
    const response = await fetch(`https://www.reddit.com/api/v1/access_token`, {
      method: 'POST',
      body: `grant_type=authorization_code&code=${code}&redirect_uri=http://localhost:5173/auth/reddit/callback`,
      headers: {
        authorization: `Basic ${encodedHeader}`,
        'Content-Type': 'application/x-www-form-urlencoded',
        'User
headers: {
        authorization: `Basic ${encodedHeader}`,
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': process.env.REDDIT_USER_AGENT || '',
      },
    });
    const body = await response.json();
    console.log(body);

im trying to authenticate reddit with my reactjs app but im getting 401 on this api call, any resources i can refer to ?
P.S i have double checked by REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET and they are correct.

Logo not aligning with Nav Bar

I’ve spent hours and days on this bug I’ve come across. Just a heads up, I’m using Bootstrap for the framework for a simpler workflow. The problem started once I added the logo to the Nav bar for my website. I’ve tried implementing different functions, but they never worked. In the index.html file, I added:

    <nav class="navbar-expand-lg bg-custom">

Which didn’t work either, here’s the index.html code:

I’ll also attach the CSS code:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Real Estate - Home</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <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=Alice&family=Quicksand:wght@400;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
    <link rel="stylesheet" href="css/style.css">
</head>
  <body>
    <nav class="navbar-expand-lg bg-custom">
        <div class="container-fluid">
          <a class="navbar-brand" href="index.html"><img src="images/ai-home-america-realty-logo.svg" alt="Home America Realty" class="logo"</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0 nav nav-underline">
              <li class="nav-item">
                <a class="nav-link" href="index.html">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="about.html">About</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="services.html">Services</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="agents.html">Agents</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="contact.html">Contact</a>
              </li>
            </ul>
            <form class="d-flex" role="search">
              <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
              <button class="btn btn-outline-success" type="submit">Search</button>
            </form>
          </div>
        </div>
      </nav>

      <footer class="footer mt-auto-py3 fixed-bottom">
        <div class="container text-center">
         
        </div>
      </footer>

      <link rel="canonical" href="https://getbootstrap.com/docs/5.3/examples/sticky-footer-navbar/">

    

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">

<link href="style.css" rel="stylesheet">

    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }

      .b-example-divider {
        width: 100%;
        height: 3rem;
        background-color: rgba(0, 0, 0, .1);
        border: solid rgba(0, 0, 0, .15);
        border-width: 1px 0;
        box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
      }

      .b-example-vr {
        flex-shrink: 0;
        width: 1.5rem;
        height: 100vh;
      }

      .bi {
        vertical-align: -.125em;
        fill: currentColor;
      }

      .nav-scroller {
        position: relative;
        z-index: 2;
        height: 2.75rem;
        overflow-y: hidden;
      }

      .nav-scroller .nav {
        display: flex;
        flex-wrap: nowrap;
        padding-bottom: 1rem;
        margin-top: -1px;
        overflow-x: auto;
        text-align: center;
        white-space: nowrap;
        -webkit-overflow-scrolling: touch;
      }

      .btn-bd-primary {
        --bd-violet-bg: #3333ff;
        --bd-violet-rgb: 112.520718, 44.062154, 249.437846;

        --bs-btn-font-weight: 600;
        --bs-btn-color: var(--bs-white);
        --bs-btn-bg: var(--bd-violet-bg);
        --bs-btn-border-color: var(--bd-violet-bg);
        --bs-btn-hover-color: var(--bs-white);
        --bs-btn-hover-bg: #6528e0;
        --bs-btn-hover-border-color: #6528e0;
        --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
        --bs-btn-active-color: var(--bs-btn-hover-color);
        --bs-btn-active-bg: #5a23c8;
        --bs-btn-active-border-color: #5a23c8;
      }

      .bd-mode-toggle {
        z-index: 1500;
      }

      .bd-mode-toggle .dropdown-menu .active .bi {
        display: block !important;
      }
    </style>

      
      <link href="style.css" rel="stylesheet">
    </head>
    <body class="d-flex flex-column h-100">
      <svg xmlns="http://www.w3.org/2000/svg" class="d-none">
        <symbol id="check2" viewBox="0 0 16 16">
          <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
        </symbol>
        <symbol id="circle-half" viewBox="0 0 16 16">
          <path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"/>
        </symbol>
        <symbol id="moon-stars-fill" viewBox="0 0 16 16">
          <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
          <path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"/>
        </symbol>
        <symbol id="sun-fill" viewBox="0 0 16 16">
          <path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
        </symbol>
      </svg>
      <div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
        <button class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center"
                id="bd-theme"
                type="button"
                aria-expanded="false"
                data-bs-toggle="dropdown"
                aria-label="Toggle theme (auto)">
          <svg class="bi my-1 theme-icon-active" width="1em" height="1em"><use href="#circle-half"></use></svg>
          <span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
        </button>
        <ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
          <li>
            <button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
              <svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#sun-fill"></use></svg>
              Light
              <svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
            </button>
          </li>
          <li>
            <button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark" aria-pressed="false">
              <svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#moon-stars-fill"></use></svg>
              Dark
              <svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
            </button>
          </li>
          <li>
            <button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto" aria-pressed="true">
              <svg class="bi me-2 opacity-50" width="1em" height="1em"><use href="#circle-half"></use></svg>
              Auto
              <svg class="bi ms-auto d-none" width="1em" height="1em"><use href="#check2"></use></svg>
            </button>
          </li>
        </ul>
      </div>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
  </body>
</html>

I’m not sure what else to do or try. Can anyone assist?

Select tag to change both link and text of a

I am interested in using form “select” dropdowns to change both the URL and the text from each “select”, both in a <p> tag.

Below is a bad example, but want to use select items to change the link text and URL.

<div class="price">
    <strong>2.00</strong>
    <span>per month</span>
</div>

<select id="planOne">
  <option value="google.com">Google</option>
  <option value="microsoft.com">Microsoft</option>
  <option value="yahoo.com">Yahoo</option>
</select>
<a id="OrderLinkOne" href="google.com">Link</a> 



$(document).ready(function(){
  $("#planOne").change(function () {
    console.log(this.value);
    $("#OrderLinkOne").attr('href', this.value);
    $(".price").children("strong").text("3.00");
    $("span").text("per week");
  });
});   

How to prevent pages from closing when out of memory

Scrolling down instagram peaks cpu after ~10000 posts

im trying to scroll down an Instagram tagged page to find the oldest posts; the page has ~15000 posts but when i reach ~10000 posts, loading new posts peaks my cpu (ryzen 5 5500U) causing the page to close due to being out of memory. also seems that the ram usage of 1 tab is capped at 4.5 gb for whatever reason despite having 20gb of ram. tried restricting the cpu affinity on a few browsers – chrome, edge – to fewer cores but the page still closed due to being out of memory after ~10000 posts. is there a means to reach the bottom of the page

is there a way to circumvent the out of memory error such as rendering the page in a lower-memory mode or preventing the webpage from closing when out of memory, or any other means of reaching the bottom of the page

Why are the transparent sprites not being drawn correctly Pixi.js v8.6.6

The code separates a tiles sheet into 40×40 sections and places them onto the pixi canvas. But the transparent tiles are not being drawn correctly. I have tried looking through the documentation but I can’t find out what is causing it.

  const tiles = 'https://static.koalabeast.com/textures/musclescupgradients/tiles.png';

  (async () => {
    const app = new PIXI.Application();
    await app.init({ width: 640, height: 440 });
    document.body.appendChild(app.view);

    const sheetTexture = await PIXI.Assets.load(tiles);
    const w = 40;
    const sheetBase = sheetTexture.baseTexture;

    const cols = Math.floor(sheetBase.width / w);
    const rows = Math.floor(sheetBase.height / w);

    const tileTextures = [];

    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        const x = col * w;
        const y = row * w;
        const frame = new PIXI.Rectangle(x, y, w, w);

        const tileTex = new PIXI.Texture({ source: sheetBase, frame });
        tileTextures.push(tileTex);

        const sprite = new PIXI.Sprite(tileTex);
        sprite.x = col * w;
        sprite.y = row * w;
        app.stage.addChild(sprite);
      }
    }
  })();

Pixi Drawn Image

Original Image

Updating HTML element causes PointerLockControls to stutter in three.js

For quite some time, I’ve had in issue in three.js where when putting any HTML element updates in my animation loop, my PointerLockControls always have a strange stutter. For example:

function animate() {
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Works completely normal. PointerLockControls feel smooth, responsive, and my framerate is normal.

But if I do this:

function animate() {
  document.querySelector("#some-element").textContent = "foobar";
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Suddenly, the PointerLockControls have a strange stutter effect, whereas if I point my cursor up slowly, the controls are stuck, but if I point my cursor up slightly faster, the controls completely jerk to the top of the screen.

Strangely enough, the framerate is not affected by this. It is just the PointerLockControls, nothing else.

Why does this issue happen, and how can I fix it? Also, I need to constantly update the element’s textContent, so “updating it only when I need to” doesn’t work.

Angular 20 + SSR: Hydration error on dynamic routes (e.g., /news/:category)

I’m using Angular 20 with server-side rendering (Angular SSR). Everything works correctly on most routes, but I encounter a hydration error on dynamic routes like /news/:category or /tools/:id. The error appears in the browser console after hydration:

main.ts:6 ERROR Error: ASSERTION ERROR: Unexpected state: no hydration info available for a given TNode, which represents a view container. [Expected=> null != undefined <=Actual]

If I change the same component to use a static route (e.g., /news/general without a route parameter), the error does not occur and hydration completes without issues.

It seems like Angular is failing to hydrate properly when dynamic route parameters are involved.

What I’ve tried:

Verified that the component renders properly in SSR and client modes.

Ensured that data fetching completes on the server before hydration.

Compared static vs dynamic route behavior — only dynamic routes trigger the error.

Looked through Angular SSR documentation, but couldn’t find a clear solution.

Questions:

What might be causing this hydration error only on dynamic routes?

Are there any examples or best practices for configuring Angular 20 SSR properly with dynamic routes?

Is there a workaround or fix for this issue?

Transforming WebGL2 canvas based on touch events?

I want a webgl canvas that I can seemlessly translate, rotate and scale with multitouch (using touch events).

So far I have a canvas that I can translate with 1 finger or scale with 2 fingers, but I’m having trouble integrating all the functionality into one.

So far I’m completely clueless how to support rotation at all.

My code:

const vertSource = `#version 300 es

in vec4 a_position;
in vec2 a_texcoord;

uniform mat4 u_matrix;

out vec2 v_texcoord;

void main() {
  gl_Position = u_matrix * a_position;
  
  v_texcoord = a_texcoord;
}`
const fragSource = `#version 300 es

precision highp float;

in vec2 v_texcoord;

uniform sampler2D u_texture;

out vec4 outColor;

void main() {
  outColor = texture(u_texture, v_texcoord);
}`
const css = `html, body, canvas {
  width: 100%;
  height: 100%;
}
body {
  overscroll-behavior-y: contain;
  overflow: hidden;
  touch-action: none;
}
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}`

function define(target, defines) {
  return Object.defineProperties(target, Object.getOwnPropertyDescriptors(defines))
}

function vec2(x, y) {
  if (x === undefined)
    x = 0
  if (y === undefined)
    y = x
  
  return define([x, y], {
    get x() { return this[0] },
    set x(value) { this[0] = value },
    get y() { return this[1] },
    set y(value) { this[1] = value },
  })
}

const mat4 = {
  translation(x, y, z = 0) {
    return [
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      x, y, z, 1,
    ]
  },
  xRotation(angle) {
    const c = Math.cos(angle)
    const s = Math.sin(angle)
    
    return [
      1,  0, 0, 0,
      0,  c, s, 0,
      0, -s, c, 0,
      0,  0, 0, 1,
    ]
  },
  yRotation(angle) {
    const c = Math.cos(angle)
    const s = Math.sin(angle)
    
    return [
      c, 0, -s, 0,
      0, 1,  0, 0,
      s, 0,  c, 0,
      0, 0,  0, 1,
    ]
  },
  zRotation(angle) {
    const c = Math.cos(angle)
    const s = Math.sin(angle)
    
    return [
       c, s, 0, 0,
      -s, c, 0, 0,
       0, 0, 1, 0,
       0, 0, 0, 1,
    ]
  },
  scale(x, y, z = 1) {
    if (y === undefined)
      y = x
    return [
      x, 0, 0, 0,
      0, y, 0, 0,
      0, 0, z, 0,
      0, 0, 0, 1,
    ]
  },
  projection(width, height, depth) {
    // Note: This matrix flips the Y axis so 0 is at the top.
    return [
       2 / width, 0, 0, 0,
       0, -2 / height, 0, 0,
       0, 0, 2 / depth, 0,
      -1, 1, 0, 1,
    ]
  },
  orthographic(left, right, bottom, top, near, far) {
    return [
      2 / (right - left), 0, 0, 0,
      0, 2 / (top - bottom), 0, 0,
      0, 0, 2 / (near - far), 0,
      
      (left + right) / (left - right),
      (bottom + top) / (bottom - top),
      (near + far) / (near - far),
      1,
    ]
  },
  mul(a, b) {
    const b00 = b[0 * 4 + 0]
    const b01 = b[0 * 4 + 1]
    const b02 = b[0 * 4 + 2]
    const b03 = b[0 * 4 + 3]
    const b10 = b[1 * 4 + 0]
    const b11 = b[1 * 4 + 1]
    const b12 = b[1 * 4 + 2]
    const b13 = b[1 * 4 + 3]
    const b20 = b[2 * 4 + 0]
    const b21 = b[2 * 4 + 1]
    const b22 = b[2 * 4 + 2]
    const b23 = b[2 * 4 + 3]
    const b30 = b[3 * 4 + 0]
    const b31 = b[3 * 4 + 1]
    const b32 = b[3 * 4 + 2]
    const b33 = b[3 * 4 + 3]
    const a00 = a[0 * 4 + 0]
    const a01 = a[0 * 4 + 1]
    const a02 = a[0 * 4 + 2]
    const a03 = a[0 * 4 + 3]
    const a10 = a[1 * 4 + 0]
    const a11 = a[1 * 4 + 1]
    const a12 = a[1 * 4 + 2]
    const a13 = a[1 * 4 + 3]
    const a20 = a[2 * 4 + 0]
    const a21 = a[2 * 4 + 1]
    const a22 = a[2 * 4 + 2]
    const a23 = a[2 * 4 + 3]
    const a30 = a[3 * 4 + 0]
    const a31 = a[3 * 4 + 1]
    const a32 = a[3 * 4 + 2]
    const a33 = a[3 * 4 + 3]
    
    return [
      b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
      b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
      b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
      b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
      b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
      b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
      b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
      b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
      b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
      b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
      b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
      b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
      b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
      b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
      b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
      b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
    ]
  },
}

const camera = vec2()
let rotation = 0
let scaling = 1
const tiles = [
  [0, 0],
  [1, 0],
  [2, 0],
  [3, 0],
  [4, 0],
  [5, 0],
  [6, 0],
  [0, 1],
  [0, 2],
  [0, 3],
  [0, 4],
  [1, 4],
  [2, 4],
  [3, 4],
  [4, 4],
  [5, 4],
  [6, 4],
  [6, 1],
  [6, 2],
  [6, 3],
]

function resizeCanvasToDisplaySize(canvas, mult = 1) {
  const width = canvas.clientWidth * mult | 0
  const height = canvas.clientHeight * mult | 0
  if (canvas.width != width || canvas.height != height) {
    canvas.width  = width
    canvas.height = height
    return true
  }
  return false
}

function createShader(type, source) {
  const shader = gl.createShader(type)
  gl.shaderSource(shader, source)
  gl.compileShader(shader)
  const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
  if (success)
    return shader
  
  console.log(gl.getShaderInfoLog(shader))
  gl.deleteShader(shader)
  return undefined
}

function createProgram(vertexShader, fragmentShader) {
  const program = gl.createProgram()
  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)
  gl.linkProgram(program)
  const success = gl.getProgramParameter(program, gl.LINK_STATUS)
  if (success)
    return program

  console.log(gl.getProgramInfoLog(program))
  gl.deleteProgram(program)
  return undefined
}

function setupTouchEvents() {
  let cam, p0, p1
  let rot, scale
  gl.canvas.ontouchstart = e => {
    const t0 = e.touches[0]
    const t1 = e.touches[1]
    const [x0, y0] = [t0.clientX, t0.clientY]
    const [x1, y1] = [t1?.clientX, t1?.clientY]
    
    if (e.touches.length == 1) {
      p0 = vec2(x0, y0)
      cam = vec2(camera.x, camera.y)
    }
    else {
      p0 = vec2(x0, y0)
      p1 = vec2(x1, y1)
      cam = vec2(camera.x, camera.y)
      scale = Math.hypot(p0.x - p1.x, p0.y - p1.y)
    }
  }
  gl.canvas.ontouchmove = e => {
    const t0 = e.touches[0]
    const t1 = e.touches[1]
    const [x0, y0] = [t0.clientX, t0.clientY]
    const [x1, y1] = [t1?.clientX, t1?.clientY]
    
    if (e.touches.length == 1) {
      camera.x = cam.x + (x0 - p0.x) / 32
      camera.y = cam.y + (y0 - p0.y) / 32
    }
    else {
      p0 = vec2(x0, y0)
      p1 = vec2(x1, y1)
      
      const newScale = Math.hypot(p0.x - p1.x, p0.y - p1.y)
      scaling = newScale / scale
      
      camera.x = cam.x + (x0 - p0.x) / 32
      camera.y = cam.y + (y0 - p0.y) / 32
    }
  }
  gl.canvas.ontouchend = e => {
    const t = e.changedTouches[0]
    const [x, y] = [t.clientX, t.clientY]
    
    
    if (t.identifier == 0) {
      
    }
  }
}

function main() {
  const style = document.createElement('style')
  style.textContent = css
  document.head.append(style)
  
  const canvas = document.createElement('canvas')
  document.body.append(canvas)
  
  define(globalThis, { gl: canvas.getContext('webgl2') })
  if (!gl)
    return
  
  // Create shader program
  const vertexShader = createShader(gl.VERTEX_SHADER, vertSource)
  const fragmentShader = createShader(gl.FRAGMENT_SHADER, fragSource)
  const program = createProgram(vertexShader, fragmentShader)
  
  // Find attribute and uniform locations
  const a_position = gl.getAttribLocation(program, 'a_position')
  const a_texcoord = gl.getAttribLocation(program, 'a_texcoord')
  const u_matrix = gl.getUniformLocation(program, 'u_matrix')

  // Create a vertex array object (attribute state)
  const vao = gl.createVertexArray()
  gl.bindVertexArray(vao)

  // Create position buffer
  const positionBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
  const positions = [
    0, 0, 0,
    0, 1, 0,
    1, 0, 0,
    1, 1, 0,
  ]
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)

  // Turn on the attribute
  gl.enableVertexAttribArray(a_position)
  gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0)

  // Create texcoord buffer
  const texcoordBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer)
  const texcoords = [
    0, 0,
    0, 1,
    1, 0,
    1, 1,
  ]
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW)

  // Turn on the attribute
  gl.enableVertexAttribArray(a_texcoord)
  gl.vertexAttribPointer(a_texcoord, 2, gl.FLOAT, true, 0, 0)

  // Create a texture
  const texture = gl.createTexture()
  gl.activeTexture(gl.TEXTURE0 + 0)
  gl.bindTexture(gl.TEXTURE_2D, texture)
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
    0, 0, 255, 255,
    255, 0, 0, 255,
    255, 0, 0, 255,
    0, 0, 255, 255,
  ]))
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
  
  // Create index buffer
  const indexBuffer = gl.createBuffer()
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
  const indices = [
    0, 1, 2,
    2, 1, 3,
  ]
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW)
  
  setupTouchEvents()

  let lastTime = 0
  function drawScene(time) {
    // Subtract the previous time from the current time
    const delta = (time - lastTime) / 1000
    // Remember the current time for the next frame.
    lastTime = time
    
    resizeCanvasToDisplaySize(gl.canvas)
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
    
    // Turn on depth testing and culling
    gl.enable(gl.DEPTH_TEST)
    gl.enable(gl.CULL_FACE)
    
    // Clear color and depth information
    gl.clearColor(0, 0, 0, 1)
    gl.clearDepth(1)
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    
    // Bind the attribute/buffer set we want
    gl.useProgram(program)
    gl.bindVertexArray(vao)
    
    const unit = 32
    let proj = mat4.projection(gl.canvas.clientWidth / unit, gl.canvas.clientHeight / unit, 400)
    proj = mat4.mul(proj, mat4.translation(camera.x, camera.y))
    proj = mat4.mul(proj, mat4.zRotation(rotation))
    proj = mat4.mul(proj, mat4.scale(scaling))
    
    tiles.forEach(([x, y]) => {
      // Transform matrix
      const matrix = mat4.mul(proj, mat4.translation(x, y))
      
      // Set the matrix
      gl.uniformMatrix4fv(u_matrix, false, matrix)
      
      // Draw the geometry
      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)
    })
    
    // Call drawScene again next frame
    requestAnimationFrame(drawScene)
  }
  requestAnimationFrame(drawScene)
}

main()

Regex for age validation in JavaScript

I have the following input in my index.html, which asks the user to enter their age

 <input type="text" placeholder="Enter your age:" id="age" />

I would like to check the input for valid values, using a regular expression. Even though the input is of type:text i want it to accept only values from 0-99 (considering the normal age of human living).
Is the following regex valid /s{0-9}{0-9} for my approach?

How to use getBoundingClientRect() considering Windows screen scale factor?

I’m using the code below to get the position of a specific element on screen :

document.getElementById('my-element').getBoundingClientRect();

For instance, it will return :

DOMRect {x: 1406, y: 356.3999938964844, width: 52, height: 52, top: 356.3999938964844, …}

On Windows 11 with Chrome, it works correctly as long as the Scale on System > Display is set to 100%. But usually on high res monitors, it’s always set bigger, like the image below. In this scenario, the coordinates returned by the command above won’t reflect the correct position of the element. How can i make the getBoundingClientRect() command work properly in any display scale on Windows 11 ?

enter image description here

How to synchronize hover time and seek time in a custom React video player using ReactPlayer?

I’m building a custom video player in React and I’m having trouble aligning the time displayed on hover over the progress bar with the actual seek time used when clicking.

Problem
I use an as the progress bar.

On onMouseMove, I calculate the percentage based on event.clientX and show a tooltip (indicadorRef) displaying the estimated time for that position.

The time displayed on hover looks visually correct.

However, when the user clicks (using onChange), the actual seek time does not exactly match the hovered time — usually off by a small fraction of a second.

I’ve tried rounding values and aligning steps, but small mismatches remain.

Example:

const handleHoverProgress = (event) => {
  if (!isPlayerReady) return;

  const rect = inputProgressRef.current.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const percentage = Math.max(0, Math.min(1, x / rect.width));

  const min = parseFloat(inputProgressRef.current.min);
  const max = parseFloat(inputProgressRef.current.max);
  const step = parseFloat(inputProgressRef.current.step) || 0.001;

  let value = min + percentage * (max - min);
  value = Math.round(value / step) * step;
  value = parseFloat(value);

  indicadorRef.current.textContent = formatTime(value * duration);
  indicadorRef.current.style.visibility = "visible";

  let indicatorX = x - indicadorRef.current.offsetWidth / 2;
  indicatorX = Math.max(0, Math.min(rect.width - indicadorRef.current.offsetWidth, indicatorX));
  indicadorRef.current.style.left = `${indicatorX}px`;
};

ReactPlayer setup:

<ReactPlayer
  ref={playerRef}
  url={currentVideoUrl}
  playing={playing}
  volume={volume}
  onProgress={({ played }) => setPlayed(played)}
  onDuration={setDuration}
  onReady={() => setIsPlayerReady(true)}
  controls={false}
  width="100%"
  height="100%"
  progressInterval={100}
/>

Range input:

<input
  ref={inputProgressRef}
  onMouseMove={handleHoverProgress}
  type="range"
  min={0}
  max={1}
  step={0.001}
  value={played}
  onChange={(e) => handleSeek(parseFloat(e.target.value))}
  className="progress-bar"
/>

What I’ve tried
Matching the tooltip value with the seek value by using the same percentage calculation.

Rounding to the nearest step to avoid floating-point issues (e.g., 0.73899999).

Using progressInterval={100} on ReactPlayer for more frequent updates.

The issue
Despite all this, the hovered time and the actual seek time don’t perfectly match after clicking. Sometimes it’s off by a small fraction of a second or by one second depending on rounding behavior.

What I’m looking for
How can I ensure that the time shown on hover exactly matches the time that the video seeks to when the user clicks?

Technologies
React

ReactPlayer

Any help or suggestions are appreciated. Thank you!

enter image description here

In Javascript/Typescript there’s a pattern for checks his own class? Or did I come up with something new?

Consider the following code:

import type { Account } from "@/models/account";
import { fromAccountToFirebase, fromFirebaseToAccount } from "@/scripts/common/map-firestore-account";
import { ImplFirestoreStorage } from "@/services/impl/impl-firestore-storage";
import type { StorageService } from "@/services/interfaces/storage-service";
import type { User } from "firebase/auth";
import { limit, where, type DocumentData, type Firestore } from "firebase/firestore/lite";
import { filter, from, map, type Observable } from "rxjs";

/** from storage-service.ts */

// base for any data mapped to an id.
export interface SupportsId<ID> {
    readonly id: ID;
}

export interface StorageService<T extends SupportsId<ID>, ID> {
    /** gets the first value with the matching id, if no value is found becomes empty. */
    getById(id: ID): Observable<T>;
    /** gets all matches that pass the filter for the max amount of entries to find, an optional
     * start index will skip the first entries.
     */
    getFiltered(filter: (value: T) => boolean, maxCount: number, startIdx?: number): Observable<T[]>;
    /** write the value into the database and raises an error if write fails. */
    update(value: T): Observable<void>;
}

/** from impl-firestore-storage.ts */

export abstract class ImplFirestoreStorage<T extends SupportsId<string>> implements StorageService<T, string> {
    // implementation for the required methods.

    public getByQuery(...constraints: QueryConstraint[]): Observable<T[]> { /** ... */ }
}

/** from impl-sql-storage.ts */

export abstract class ImplSqlStorage<T extends SupportsId<string>> implements StorageService<T, string> {
    // implementation for the required methods.
}

/** from account-storage-service.ts */

export class AccountsStorageService extends ImplFirestoreStorage<Account> {

    /** get the account data from the matching user registered with the account. */
    public getByUser(user: User): Observable<Account> {

        if (this instanceof ImplFirestoreStorage) {
            // use more optimized query if using firestore.
            return from(this.getByQuery(limit(1), where("userId", "==", user.uid))).pipe(
                filter(array => array.length !== 0),
                map(array => array[0]));
        }

        // fallback with the generic query method.
        return (this as StorageService<Account, string>).getFiltered(account => account.userId === user.uid, 1).pipe(
            filter(array => array.length !== 0),
            map(array => array[0]));
    }
}

In this code there’s a common database signature called StorageService that ensure the presence of some method, then the AccountsStorageService is a global class to interact with a database of accounts.

In the code base I have 2 implementations to interact with a firestore database and a sql database. Why I have like this? In this project we are switching between using either a firestore or classic sql database and managed to make an implementation for both. Don’t ask further about this.

But here an interesting thing, the firestore has a query system for more optimised requests and the final class AccountsStorageService checks if itself is extended either from the firestore implementation to use that more optimised system or if not use the generic one.

I find this interesting even if I’ve never seen such pattern anywhere, I come up with this thing. I had to use that casting to fight againts typescript that flagged after the if the instance as never.
I imagine at compile/bundle time the script will be optimized to use the content of the if directly.

Even if this question looks like a code review, and I know those aren’t allowed in this website, I actually want to know if this is a code patter that exists. That’s the question.
This thing I wrote works perfectly!

Javascript formatter that allows excluding sections?

I’m looking for a JavaScript formatter that allows skipping sections. I’m not too picky about the formatting style, but not being able to exclude a section is a deal-breaker, so Prettier is out.

Example of a section I want to exclude from formatting:

    class X {
        ...

        // stop-formatting
        get lines() { return this.#lines.length                  }
        get col()   { return this.#x + 1                         }
        get row()   { return this.#y + 1                         }
        get done()  { return this.#y >= this.#lines.length       }
        get eol()   { return this.#x >= this.current_line.length }
        // resume-formatting
    }

How could I log the height of a dynamically created element?

I am working on app that lets users manage a list of contacts. I have a function for a mobile view that creates a list of elements that displays contact information. Each of these elements have additional elements appended to them so as to show a contacts picture, name, email address, organization and role that they are associated with. The contact’s organization and role are displayed in a p tag together. This p tag is assigned to a variable named contactListOrganizationRoleAndElement. I noticed that sometimes when viewing the contact list elements in a mobile setting these p tags increase their height so as to accommodate long text and that causes them to display some of the text on a new line and this doesn’t look good. I figured that in the function that I am using I could detect whenever a p tag exceeded 18px in height and adjust the second text to be an ellipsis to indicate to the user that the text was too long to fit and also maintain a consistent appearance for these contact elements. I am able to log the p tag’s via a variable called contactListOrganizationAndRoleElement to my console, and when I hover over the logged elements in the console a tooltip appears giving the dimension of the elements. However, when I try to log the height of these p tags to the console it either gives me a blank line or a value of 0. I found this strange because I managed to log further style information about the p tags via the contactListOrganizationAndRoleElement variable such as its fontWeight and margin properties. I figured then that I should also be able to log the height from the variable’s style property but the height property from style returns a blank line. I also tried using clientHeight and offSetHeight properties from the variable but these are returning a value of 0. How might I log the height of the variable contactListOrganizationAndRoleElement so as to know the height of these p tags?

enter image description here

async function renderMobileContactsListContent() {
     const userId = sessionStorage.getItem("user");
    const user = await getUser(userId);
    const userContacts = await getUserContacts(userId)

    const contactsListUserImage = document.querySelector("#contacts-user-image");
    if (user.user_image !== null && user.user_image !== './images/user-5-svgrepo-com.svg') {
        contactsListUserImage.setAttribute("src", user.user_image);
        contactsListUserImage.style.borderRadius = "50%";
    }

    const contactsUserHeaderNameContainer = document.querySelector("#contacts-user-header-name-container");
    // contactsUserHeaderNameContainer.style.margin = "0px 0px 0px 10px"
    const contactsHeaderUserNameElement = document.querySelector("#mobile-contact-header-user-name");
    contactsHeaderUserNameElement.style.margin = "0px";
    const contactsHeaderUserEmailAddressElement = document.querySelector("#contact-header-user-email")
    // contactsHeaderUserEmailAddressElement.style.margin = "0px 0px 16px 0px";

    const contactsUserNameElement = document.querySelector("#contacts-user-name");
    contactsUserNameElement.style.margin = "0px";
    const contactsUserEmailAddressElement = document.querySelector("#contacts-user-email");
    contactsUserEmailAddressElement.style.margin = "0px";
    contactsUserNameElement.innerHTML = `${user.firstname}'s Contacts`;
    // contactsUserNameElement.style.fontSize = "xx-large"
    contactsUserNameElement.style.fontFamily = "Arial"
    // contactsUserEmailAddressElement.innerHTML = `${user.emailaddress}`

    contactsHeaderUserNameElement.innerHTML = `${user.firstname} ${user.lastname}`;
    contactsHeaderUserEmailAddressElement.innerHTML = user.emailaddress;

    console.log(userContacts)

    userContacts.sort(function(a, b) {
        if (a.firstname < b.firstname) {
            return -1;
        }
        if (a.firstname < b.firstname) {
            return 1;
        }
        
        var aFirstChar = a.firstname.charAt(0);
        var bFirstChar = b.firstname.charAt(0);
        if (aFirstChar > bFirstChar) {
          return 1;
        } else if (aFirstChar < bFirstChar) {
          return -1;
        } else {
          var aLastChar = a.lastname.charAt(0);
          var bLastChar = b.lastname.charAt(0);
          if (aLastChar === "") {
            aLastChar = "z"
          }
          if (bLastChar === "") {
            bLastChar = "z"
          }
          if (aLastChar > bLastChar) {
            return 1;
          } else if (aLastChar < bLastChar) {
            return -1;
          } else {
            return 0;
          }    
        }
      });

    const contactsListContainer = document.createElement("div");
    const contactsHeaderContainer = document.createElement("div");
    contactsHeaderContainer.style.display = "flex";
    // contactsHeaderContainer.style.justifyContent = "space-between"
    contactsHeaderContainer.style.alignItems = "center"
    // contactsHeaderContainer.style.width = "25%";
    contactsHeaderContainer.style.backgroundColor = "ghostwhite"
    // contactsHeaderContainer.style.marginBottom = "5px"
    contactsHeaderContainer.style.padding = "5px"
    const myContactsHeaderElement = document.createElement("h2");
    myContactsHeaderElement.innerHTML = "My Contacts"
    myContactsHeaderElement.style.width = "140px"
    myContactsHeaderElement.style.margin = "0"
    myContactsHeaderElement.style.marginLeft = "5px"
    // myContactsHeaderElement.style.marginRight = "10px"
    const numberOfContactsElement = document.createElement("h2");
    numberOfContactsElement.innerHTML = userContacts.length;
    numberOfContactsElement.style.display = "inline-flex";
    numberOfContactsElement.style.justifyContent = "center";
    numberOfContactsElement.style.alignItems = "center";
    numberOfContactsElement.style.width = "15px";
    numberOfContactsElement.style.height = "15px";
    numberOfContactsElement.style.backgroundColor = "navy";
    numberOfContactsElement.style.color = "white"
    numberOfContactsElement.style.padding = "10px";
    numberOfContactsElement.style.borderRadius = "50%";
    numberOfContactsElement.style.margin = "0"
    contactsListContainer.style.position = "absolute";
    contactsListContainer.style.top = "28.2%"
    contactsListContainer.style.width = "100%"
    const contactsList = document.createElement("ul");
    contactsList.style.listStyle = "none";
    contactsList.style.padding = "0"
    contactsList.style.margin = "0"
    // contactsList.style.height = "100%";
    // contactsList.style.overflow = "auto"
    // contactsList.style.zIndex = "5"
    userContacts.forEach(contact => {
        const contactListItem = document.createElement("div");
        contactListItem.style.display = "flex";
        contactListItem.style.flexDirection = "row";
        contactListItem.style.height = "100px"
        contactListItem.style.borderTop = "1px solid gray";
        contactListItem.style.borderBottom = "1px solid gray";
        contactListItem.style.backgroundColor = "aliceblue"
        contactListItem.style.marginTop = "1px";
        contactListItem.style.marginBottom = "1px";
        contactListItem.setAttribute("contactId", contact.contact_id)

        contactListItem.addEventListener("mouseover", function() {
            contactListItem.style.backgroundColor = "lightgrey";
        });

        contactListItem.addEventListener("mouseout", function() {
            contactListItem.style.backgroundColor = "aliceblue";
        });

        contactListItem.addEventListener("click", function(event) {
            
                // contactListItem.style.backgroundColor = "green";
                
                function saveDataToURL(url, data) {
                    const urlObject = new URL(url);
                    const params = new URLSearchParams(urlObject.search);
                
                    for (const key in data) {
                        if (data.hasOwnProperty(key)) {
                            params.set(key, data[key]);
                        }
                    }
                    urlObject.search = params.toString();
                    return urlObject.toString();
                }
                
                const myURL = `${rootUrl}/contact_${contact.contact_id}`;
                const myData = {
                    name: `${contact.firstname} ${contact.lastname}`,
                    // age: 30,
                    // city: "New York"
                };
                
                const newURL = saveDataToURL(myURL, myData);
                console.log(newURL);
                // Expected output: "https://example.com/page?name=John+Doe&age=30&city=New+York"
                window.location.href = newURL
        })

        const contactListItemImageContainer = document.createElement("div");
        contactListItemImageContainer.style.display = "flex";
        contactListItemImageContainer.style.alignItems = "center";
        // contactListItemImageContainer.style.width = "10%"
        contactListItemImageContainer.style.padding = "10px"
        const contactListItemImage = document.createElement("img");
        contactListItemImage.style.width = "90px";
        contactListItemImage.style.height = "100%";
        contactListItemImage.style.border = "0.5px solid grey";
        contactListItemImage.style.borderRadius = "50%"
        contactListItemImage.style.backgroundColor = "gainsboro";
        contactListItemImage.style.objectFit = "cover";
        
        if (contact.contact_image !== null && contact.contact_image !== "./images/user-2-svgrepo-com.svg") {
            contactListItemImage.setAttribute("src", contact.contact_image);
        } else {
            contactListItemImage.setAttribute("src", "./images/user-2-svgrepo-com.svg");
        }

        // contactListItem.innerHTML = `${contact.firstname} ${contact.lastname}`;
        const contactListNameContainer = document.createElement("div");
        contactListNameContainer.style.position = "relative";
        contactListNameContainer.style.display = "flex";
        contactListNameContainer.style.flexDirection = "column"
        contactListNameContainer.style.justifyContent = "center";
        contactListNameContainer.style.alignItems = "center";
        contactListNameContainer.style.width = "100%"
        const contactListNameElement = document.createElement("h3");
        contactListNameElement.style.margin = "0";
        contactListNameElement.innerHTML = `${contact.firstname} ${contact.lastname}`;
        const contactListEmailElement = document.createElement("p");
        contactListEmailElement.style.fontStyle = "italic"
        contactListEmailElement.style.margin = "0"

        
        console.log(contact)
        
        if (contact.emailaddress !== null && contact.emailaddress !== "") {
            contactListEmailElement.innerHTML = contact.emailaddress;
        } else {
            contactListEmailElement.innerHTML = "text";
            contactListEmailElement.style.visibility = "hidden";
        }

        const contactListOrganizationAndRoleElement = document.createElement("p");
        contactListOrganizationAndRoleElement.style.fontWeight = "bolder";
        contactListOrganizationAndRoleElement.style.margin = "0";
    
        if (contact.organization !== null && contact.organization !== "" && contact.organization_role !== null && contact.organization_role !== "") {
            contactListOrganizationAndRoleElement.innerHTML = `${contact.organization} || ${contact.organization_role}`
        } else if (contact.organization !== null && contact.organization !== "" || contact.organization_role === null && contact.organization_role === "") {
            contactListOrganizationAndRoleElement.innerHTML = `${contact.organization}`
        } else if (contact.organization === null && contact.organization === "" || contact.organization_role !== null && contact.organization_role !== "") {
            contactListOrganizationAndRoleElement.innerHTML = `${contact.organization_role}`
        } else {
            contactListOrganizationAndRoleElement.innerHTML = "text"
            contactListOrganizationAndRoleElement.style.visibility = "hidden"
        }

        //This is where my issue is.

        //These work...
        console.log(contactListOrganizationAndRoleElement)
        console.log(contactListOrganizationAndRoleElement.style.fontWeight)
        console.log(contactListOrganizationAndRoleElement.style.margin)
        //None of these work...
        console.log(contactListOrganizationAndRoleElement.style.height)
        console.log(contactListOrganizationAndRoleElement.clientHeight)
        console.log(contactListOrganizationAndRoleElement.offsetHeight)

        const contactListFavoritesStarIconContainer = document.createElement("div");
        contactListFavoritesStarIconContainer.style.display = "flex";
        contactListFavoritesStarIconContainer.style.justifyContent = "center";
        contactListFavoritesStarIconContainer.style.alignItems = "center"
        // contactListFavoritesStarIconContainer.style.width = "10%";
        contactListFavoritesStarIconContainer.style.padding = "10px"
        const contactListFavoriteStarImg = document.createElement("img");
        contactListFavoriteStarImg.classList.add("contact-favorite-icon")
        contactListFavoriteStarImg.style.width = "60px"

        console.log(contact.favorite)

        // contactListFavoriteStarImg.addEventListener("click", function(event) {
        //     // event.preventDefault()
        //     updateContactFavorite()
        // }, false)
        
        contactListFavoriteStarImg.setAttribute("src", "./images/star-gold-svgrepo-com.svg");
        if (contact.favorite === null || contact.favorite === false) {
            contactListFavoriteStarImg.style.visibility = "hidden"
        } else {
            contactListFavoriteStarImg.style.display = "block"
        }
        
        contactListItemImageContainer.appendChild(contactListItemImage);
        contactListNameContainer.appendChild(contactListNameElement);
        contactListNameContainer.appendChild(contactListEmailElement);
        contactListNameContainer.appendChild(contactListOrganizationAndRoleElement)
        contactListItem.appendChild(contactListItemImageContainer)
        contactListItem.appendChild(contactListNameContainer)
        contactListFavoritesStarIconContainer.appendChild(contactListFavoriteStarImg);
        contactListItem.appendChild(contactListFavoritesStarIconContainer);
        contactsList.appendChild(contactListItem)
    });
    contactsHeaderContainer.append(myContactsHeaderElement)
    contactsHeaderContainer.appendChild(numberOfContactsElement)
    contactsListContainer.appendChild(contactsHeaderContainer)
    contactsListContainer.appendChild(contactsList)
    document.body.appendChild(contactsListContainer)
}