2D javascript game: some bullets stop before reaching target [closed]

URL to my (in-progress) game: http://shaunutter.com/code/tower/

I’m trying to create a game where you place “turrets” on a grid map and the enemy goes from the left side to the right side, finding the shortest path around the turrets. So if you arrange turrets like a maze, the enemy will go through the maze. The turrets fire “bullets” at the enemy when in range.

The issue is some bullets seem to stop moving before reaching the target. If you place one “turret” on the map, the last bullet fired stops after moving only a few pixels. It’s easier to see the issue when you place multiple turrets on the map.

//----------------------------------------------------------------------------------//
//--------------------------------- number values ----------------------------------//
//----------------------------------------------------------------------------------//

var tileWidth = 36;
var tileHeight = 36;
var mapWidth = 15; // number of tiles
var mapHeight = 15; // number of tiles
var imageWidth = 22; // turret and enemy images
var imageHeight = 22; // turret and enemy images
var bulletSize = 2;
var bulletSpeed = 4; // pixels bullet moves per interval
var turretRange = 96; // fires bullet when enemy in range
var hitRange = 10; // counts as hit when bullet in range
var fireRate = 20; // how many intervals until new bullet is fired

//----------------------------------------------------------------------------------//
//------------------------------------- colors -------------------------------------//
//----------------------------------------------------------------------------------//

var gridColor1 = "#000000";
var gridColor2 = "#0d0e0f";
var borderColor = "#17181a";
var turretTileColor = "#2e3133";
var textColor = "#5d6166";
var bulletColor1 = "#00ebb4";
var bulletColor2 = "#ff0066";
var bulletColor3 = "#6200ff";
var bulletColor4 = "#00a6ff";
var bulletColor5 = "#a100ff";

//----------------------------------------------------------------------------------//
//----------------------------------- variables ------------------------------------//
//----------------------------------------------------------------------------------//

var selectedTurretButton;
var selectedTurret;
var activeTurrets = [];
var activeBullets = [];
var enemy;
var enemyPos = 0;
var enemyActive = false;
var interval = null;

//----------------------------------------------------------------------------------//
//-------------------------- variables used to find path ---------------------------//
//----------------------------------------------------------------------------------//

var start = [(mapHeight-1)/2, 0];
var end = [(mapHeight-1)/2, mapWidth-1];
var path;

//----------------------------------------------------------------------------------//
//---------------------------- matrix used to find path ----------------------------//
//----------------------------------------------------------------------------------//

var matrix = [];

for (var i = 0; i < mapHeight; i++)
{
    matrix.push(new Array(0));
}

var col = 0;

for (var m = 0; m < matrix.length; m++)
{
    col = m;

    for (var n = 0; n < mapWidth; n++)
    {
        matrix[col].push(0);
    }
}

//----------------------------------------------------------------------------------//
//---------------------------- call find path function -----------------------------//
//----------------------------------------------------------------------------------//

path = findPath(start, end);

//----------------------------------------------------------------------------------//
//------------------------- find shortest path for enemies -------------------------//
//----------------------------------------------------------------------------------//

function findPath(position, end)
{
    var temp = [];

    for (var a = 0; a < matrix.length; a++)
    {
        temp[a] = matrix[a].slice();
    }

    var queue = [];
    temp[position[0]][position[1]] = 1;
    queue.push([position]);

    while (queue.length > 0)
    {
        var path = queue.shift();
        var pos = path[path.length-1];

        var direction =
        [
            [pos[0]+1, pos[1]],
            [pos[0], pos[1]+1],
            [pos[0]-1, pos[1]],
            [pos[0], pos[1]-1]
        ];

        for (var d = 0; d < direction.length; d++)
        {
            if (direction[d][0] == end[0] && direction[d][1] == end[1])
            {
                return path.concat([end]); 
            }

            if (direction[d][0]<0 || direction[d][0]>=temp.length || direction[d][1]<0 || direction[d][1]>=temp[0].length || temp[direction[d][0]][direction[d][1]]!=0)
            { 
                continue;
            }

            temp[direction[d][0]][direction[d][1]] = 1;
            queue.push(path.concat([direction[d]])); 
        }
    }
}

//----------------------------------------------------------------------------------//
//---------------------------- create map and interface ----------------------------//
//----------------------------------------------------------------------------------//

function createMap()
{
    // create borders

    for (var b = 0; b < 6; b++)
    {
        var border = document.createElement("div");
        border.style.position = "absolute";
        border.style.backgroundColor = borderColor;

        if (b < 2) // top, bottom borders
        {
            border.style.width = tileWidth*(mapWidth+1) + "px";
            border.style.height = tileHeight/2 + "px";
        }
        else // two left, two right borders
        {
            border.style.width = tileWidth/2 + "px";
            border.style.height = tileHeight*(mapHeight/2) + "px";
        }

        if (b > 3) // two right borders
        {
            border.style.left = tileWidth*(mapWidth+1) + "px";
        }
        else // top, bottom, two left borders
        {
            border.style.left = tileWidth/2 + "px";
        }

        if (b == 1) // bottom border
        {
            border.style.top = tileHeight*(mapHeight+1) + "px";
        }
        else if (b%2) // left-bottom, right-bottom borders
        {
            border.style.top = tileHeight*((mapHeight+3)/2) + "px";
        }
        else // top, left-top, right-top borders
        {
            border.style.top = tileHeight/2 + "px";
        }

        document.body.appendChild(border);
    }

    // create grid tiles

    for (var w = 0; w < mapWidth; w++)
    {
        for (var h = 0; h < mapHeight; h++)
        {
            var tile = document.createElement("div");
            tile.style.width = tileWidth + "px";
            tile.style.height = tileHeight + "px";
            tile.style.position = "absolute";
            tile.style.left = tileWidth*(w+1) + "px";
            tile.style.top = tileHeight*(h+1) + "px";

            if (w%2 && h%2 || w%2 == 0 && h%2 == 0)
            {
                tile.style.backgroundColor = gridColor1;
            }
            else 
            {
                tile.style.backgroundColor = gridColor2;
            }

            tile.setAttribute("xPos", w); // used to update the matrix when tile is filled with turret
            tile.setAttribute("yPos", h); // used to update the matrix when tile is filled with turret
            tile.setAttribute("filled", "false"); // used when placing turrets on tiles
            tile.addEventListener("mouseover", tileMouseOver);
            tile.addEventListener("click", tileClick);
                document.body.appendChild(tile);
        }
    }

    // create turret buttons

    for (var t = 1; t < 6; t++)
    {
        var turretButton = document.createElement("div");
        turretButton.style.width = tileWidth-2 + "px";
        turretButton.style.height = tileHeight-2 + "px";
        turretButton.style.position = "absolute";
        turretButton.style.left = tileWidth*(mapWidth+t+1) + "px";
        turretButton.style.top = tileHeight*2+1 + "px";
        turretButton.style.backgroundColor = turretTileColor;
        turretButton.setAttribute("id", "turretButton"+String(t)); // used when clicking on turret button thats already selected
        turretButton.setAttribute("bulletColor", window["bulletColor"+String(t)]); // used when creating bullets
        turretButton.addEventListener("click", turretButtonClick);
        var img = document.createElement("img");
        img.src = "images/turret-"+String(t)+".png";
        img.style.width = imageWidth + "px";
        img.style.height = imageHeight + "px";
        img.style.position = "relative";
        img.style.left = (tileWidth-2-imageWidth)/2 + "px";
        img.style.top = (tileHeight-2-imageHeight)/2 + "px";
        turretButton.appendChild(img);
        document.body.appendChild(turretButton);
    }

    // create start button

    var startButton = document.createElement("div");
    startButton.style.width = tileWidth*5-2 + "px";
    startButton.style.position = "absolute";
    startButton.style.left = tileWidth*(mapWidth+2) + "px";
    startButton.style.top = tileHeight/2 + "px";
    startButton.style.backgroundColor = borderColor;
    startButton.innerHTML = "START";
    startButton.style.padding = "9px 0 10px 0";
    startButton.style.color = textColor;
    startButton.style.textAlign = "center";
    startButton.style.cursor = "default";
    startButton.id = "startButton";
    startButton.addEventListener("click", startButtonClick);
    document.body.appendChild(startButton);
}

//----------------------------------------------------------------------------------//
//----------------------------- placing turrets on map -----------------------------//
//----------------------------------------------------------------------------------//

function turretButtonClick()
{
    if (selectedTurret == null) // if no turret selected
    {
        // select turret button

        selectedTurretButton = this;
        selectedTurretButton.style.outline = "2px solid " + textColor;

        // create copy of turret to be placed on map

        selectedTurret = this.cloneNode(true);
        selectedTurret.style.pointerEvents = "none";
        document.body.appendChild(selectedTurret);
    }
    else if (this.getAttribute("id") == selectedTurret.getAttribute("id")) // if turret button already selected
    {
        // deselect turret button

        this.style.outline = "none";
        document.body.removeChild(selectedTurret);
        selectedTurretButton = null;
        selectedTurret = null;
    }
}

function tileMouseOver()
{
    if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
    {
        // move turret to this tile

        selectedTurret.style.left = parseInt(this.style.left)+1 + "px";
        selectedTurret.style.top = parseInt(this.style.top)+1 + "px";
    }
}

function tileClick()
{
    if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
    {
        // make turret active, deselect turret and turret button

        activeTurrets.push(selectedTurret);
        selectedTurretButton.style.outline = "none";
        selectedTurretButton = null;
        selectedTurret.setAttribute("fireCount", 0);
        selectedTurret.style.outline = "none";
        selectedTurret = null;

        // set tile to filled, update matrix and enemy path

        this.setAttribute("filled", "true");
        matrix[this.getAttribute("yPos")][this.getAttribute("xPos")] = 1;
        path = findPath(path[enemyPos], end);
        enemyPos = 0;
    }
}

//----------------------------------------------------------------------------------//
//------------------------ on start button click, add enemy ------------------------//
//----------------------------------------------------------------------------------//

function startButtonClick()
{
    // disable start button

    this.style.pointerEvents = "none";
    this.style.opacity = 0.5;

    // create enemy

    enemy = document.createElement("img");
    enemy.src = "images/enemy.png";
    enemy.style.width = imageWidth + "px";
    enemy.style.height = imageHeight + "px";
    enemy.style.position = "absolute";
    enemy.style.left = (tileWidth-imageWidth)/2 + "px";
    enemy.style.top = tileHeight*(mapHeight/2+1)-imageHeight/2 + "px";
    enemy.style.pointerEvents = "none";
    document.body.appendChild(enemy);
    enemyActive = true;
    path = findPath(start, end); // find enemy path
    interval = setInterval(function(){moveStuff();}, 10); // set interval for moving enemies and bullets
}

//----------------------------------------------------------------------------------//
//---------------------------- move enemies and bullets ----------------------------//
//----------------------------------------------------------------------------------//

function moveStuff()
{
    var currentX = parseInt(enemy.style.left);
    var currentY = parseInt(enemy.style.top);

    // move enemy along path

    if (enemyPos < path.length) // if enemy has not completed path
    {
        var targetX = tileWidth*(path[enemyPos][1]+1)+(tileWidth-imageWidth)/2;
        var targetY = tileHeight*(path[enemyPos][0]+1)+(tileHeight-imageHeight)/2;

        if (currentX < targetX)
        {
            currentX++;
            enemy.style.left = currentX + "px";
        }
        else if (currentX > targetX)
        {
            currentX--;
            enemy.style.left = currentX + "px";
        }
        else if (currentY < targetY)
        {
            currentY++;
            enemy.style.top = currentY + "px";
        }
        else if (currentY > targetY)
        {
            currentY--;
            enemy.style.top = currentY + "px";
        }
        
        if (currentX == targetX && currentY == targetY) // if enemy reached target tile
        {
            enemyPos++; // set enemy to next position in path
        }
    }
    else // enemy completed path
    {
        if (currentX == tileWidth*(mapWidth+1)+(tileWidth-imageWidth)/2) // if enemy reached final off-map tile
        {
            // remove enemy and interval

            enemyActive = false;
            clearInterval(interval);
            document.body.removeChild(enemy);
            enemyPos = 0;

            // reset start button

            document.getElementById("startButton").style.pointerEvents = "auto";
            document.getElementById("startButton").style.opacity = 1;
        }
        else // enemy has not reached final off-map tile
        {
            // move enemy to the right

            currentX++;
            enemy.style.left = currentX + "px";
        }
    }

    // create bullets

    for (var s = 0; s < activeTurrets.length; s++)
    {
        var turret = activeTurrets[s];
        var turretX = parseInt(turret.style.left);
        var turretY = parseInt(turret.style.top);
        var turretWidth = parseInt(turret.style.width);
        var turretHeight = parseInt(turret.style.height);
        var turretFireCount = parseInt(turret.getAttribute("fireCount"))

        // if enemy active and fire count is 0 and enemy in range of turret

        if (enemyActive && turretFireCount==0 && Math.abs(turretX-currentX)<turretRange && Math.abs(turretY-currentY)<turretRange)
        {
            // create bullet

            var bullet = document.createElement("div");
            bullet.style.width = bulletSize + "px";
            bullet.style.height = bulletSize + "px";
            bullet.style.position = "absolute";
            bullet.style.left = turretX+turretWidth/2-bulletSize/2 + "px";
            bullet.style.top = turretY+turretHeight/2-bulletSize/2 + "px";
            bullet.style.backgroundColor = turret.getAttribute("bulletColor");
            bullet.style.pointerEvents = "none";
            activeBullets.push(bullet);
            document.body.appendChild(bullet);
        }

        if (turretFireCount == fireRate) // if fire count equals fire rate
        {
            // reset fire count to 0

            turret.setAttribute("fireCount", 0);
        }
        else // fire count does not equal fire rate
        {
            // increase fire count by 1

            turret.setAttribute("fireCount", turretFireCount+1);
        }
    }

    // move bullets

    for (var u = 0; u < activeBullets.length; u++)
    {
        var currentBullet = activeBullets[u];
        var bulletX = parseInt(currentBullet.style.left);
        var bulletY = parseInt(currentBullet.style.top);
        var enemyX = parseInt(enemy.style.left) + parseInt(enemy.style.width)/2;
        var enemyY = parseInt(enemy.style.top) + parseInt(enemy.style.height)/2;

        // if enemy not active or bullet hit target

        if (enemyActive == "false" || Math.abs(bulletX-enemyX) < hitRange && Math.abs(bulletY-enemyY) < hitRange)
        {
            // remove bullet

            document.body.removeChild(currentBullet);
            activeBullets.splice(u);
        }
        else // enemy is active and bullet has not hit target
        {
            // move bullet

            var differenceX = enemyX-bulletX;
            var differenceY = enemyY-bulletY;
            var distance = Math.sqrt(differenceX*differenceX + differenceY*differenceY);

            if (distance <= bulletSpeed) // if distance between bullet and enemy less than or equal to bullet speed
            {
                // move bullet directly to enemy

                currentBullet.style.left = enemyX + 'px';
                currentBullet.style.top = enemyY + 'px';
            }
            else
            {
                // move bullet towards enemy

                var moveX = differenceX/distance*bulletSpeed;
                var moveY = differenceY/distance*bulletSpeed;
                currentBullet.style.left = (bulletX+moveX) + 'px';
                currentBullet.style.top = (bulletY+moveY) + 'px';
            }
        }
    }
}

//----------------------------------------------------------------------------------//
//--------------------------- on window load, create map ---------------------------//
//----------------------------------------------------------------------------------//

window.onload = function()
{
    createMap();
}
<html>
<head>
    <title>Tower Defense</title>

    <style>
        body
        {
            background-color: #000000;
            font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
            font-weight: bold;
            font-size: 14px;
        }
    </style>

    <script type="text/javascript" src="script.js"></script>
</head>
<body>
</body>
</html>

AWS Lambda + Puppeteer + Cloudflare

I am working with AWS Lambda and Puppeteer, which comes with its own challenges vs working locally with puppeteer. I use 2captcha services occasionally, and it shows that it has a Cloudflare solving abilities, which is exactly what I am needing. However, when using the solution they provide here https://github.com/2captcha/cloudflare-demo and adapting puppeteer for AWS lambda the Cloudflare page seems to in an endless loop of being solved then resting and never making it past it. Locally and with puppeteer being headless, it works just fine. I can’t seem to pinpoint what’s causing it to not work in AWS Lambda. Here is the current code I am trying to run, with no success. I have tried proxies as well in case it was that causing issues and that didn’t help either.

import chromium from '@sparticuz/chromium';
import { Solver } from '2captcha-ts';
import puppeteerExtra from 'puppeteer-extra';
import pluginStealth from 'puppeteer-extra-plugin-stealth';

export async function handler() {
    const viewport = {
        deviceScaleFactor: 1,
        hasTouch: false,
        height: 1080,
        isLandscape: true,
        isMobile: false,
        width: 1920
    };

    const puppeteerExtraDefault = puppeteerExtra.default;
    puppeteerExtraDefault.use(pluginStealth());

    const args = chromium.args;
    const browser = await puppeteerExtraDefault
        .launch({
            args: args,
            defaultViewport: viewport,
            executablePath: await chromium.executablePath(),
            headless: 'shell'
        });
    console.log('browser loaded');
    const [page] = await browser.pages();
    page.on('console', (msg) => {
        console.log('[PAGE LOG]', msg.text());
    });

    await page.evaluateOnNewDocument(`
console.log('Script being injected');
console.clear = () => console.log('Console was cleared')
const i = setInterval(() => {
    if (window.turnstile) {
        clearInterval(i)
        window.turnstile.render = (a, b) => {
            let params = {
                sitekey: b.sitekey,
                pageurl: window.location.href,
                data: b.cData,
                pagedata: b.chlPageData,
                action: b.action,
                userAgent: navigator.userAgent,
                json: 1
            }
            // we will intercept the message in puppeeter
            console.log('intercepted-params:' + JSON.stringify(params))
            window.cfCallback = b.callback
            return
        }
    }
}, 50)
    `);


    await page.setUserAgent(
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
    );
    const userAgent = await page.evaluate(() => navigator.userAgent);
    console.log('Current user agent:', userAgent);

    const solver = new Solver('my2CaptchaKey'); // For secrecy 
    page.on('console', async (msg) => {
        const txt = msg.text()
        if (txt.includes('intercepted-params:')) {
            const params = JSON.parse(txt.replace('intercepted-params:', ''))
            console.log(params)

            try {
                console.log(`Solving the captcha...`)
                const res = await solver.cloudflareTurnstile(params)
                console.log(`Solved the captcha ${res.id}`)
                console.log(res)
                await page.evaluate((token) => {
                    // @ts-ignore
                    cfCallback(token)
                }, res.data)
            } catch (e) {
                console.log(e.err)
                return process.exit()
            }
        } else {
            return;
        }
    })

    // 2 captchas own demo page
    await page.goto('https://2captcha.com/demo/cloudflare-turnstile-challenge')
    // Wait for a total of 45 seconds for the page to load and the cloudflare to be solved
    try {
        await page.waitForSelector('#root > div._layout_1smur_1', { timeout: 45000 });

    } catch (e) {
        console.error('Error waiting for the page to load:', e);
        await browser.close();
        return;
    }

    console.log('Page loaded successfully');
    await browser.close();
    return '';
}

useEffect with const Dependency Parameters Question?

I have this code:

const EditPost = ({
  posts, handleEdit, editBody, setEditBody, editTitle, setEditTitle
}) => {
    const { id } = useParams();
    const post = posts.find(post => (post.id).toString() === id);

    useEffect(() => {
        if (post) {
            setEditTitle(post.title);
            setEditBody(post.body);
        }
    }, [post, setEditTitle, setEditBody])

    return (
        <div> nothing here yet... </div>
    )
}

This example is from a youtube.com video. Why did the author put post, setEditTitle, and setEditBody variables in the dependency array? What effect does that have? Why bother even having useEffect() even at all?

This is from React JS Full Course for Beginners | Complete All-in-One Tutorial | 9 Hours
You can see the same code from:
Ch 19: Axios API Requests
6:29:42

Thanks!

2D javascript game: some bullets stop before reaching target

URL to my (in-progress) game: http://shaunutter.com/code/tower/

I’m trying to create a game where you place “turrets” on a grid map and the enemy goes from the left side to the right side, finding the shortest path around the turrets. So if you arrange turrets like a maze, the enemy will go through the maze. The turrets fire “bullets” at the enemy when in range.

The issue is some bullets seem to stop moving before reaching the target. If you place one “turret” on the map, the last bullet fired stops after moving only a few pixels. It’s easier to see the issue when you place multiple turrets on the map.

//----------------------------------------------------------------------------------//
//--------------------------------- number values ----------------------------------//
//----------------------------------------------------------------------------------//

var tileWidth = 36;
var tileHeight = 36;
var mapWidth = 15; // number of tiles
var mapHeight = 15; // number of tiles
var imageWidth = 22; // turret and enemy images
var imageHeight = 22; // turret and enemy images
var bulletSize = 2;
var bulletSpeed = 4; // pixels bullet moves per interval
var turretRange = 96; // fires bullet when enemy in range
var hitRange = 10; // counts as hit when bullet in range
var fireRate = 20; // how many intervals until new bullet is fired

//----------------------------------------------------------------------------------//
//------------------------------------- colors -------------------------------------//
//----------------------------------------------------------------------------------//

var gridColor1 = "#000000";
var gridColor2 = "#0d0e0f";
var borderColor = "#17181a";
var turretTileColor = "#2e3133";
var textColor = "#5d6166";
var bulletColor1 = "#00ebb4";
var bulletColor2 = "#ff0066";
var bulletColor3 = "#6200ff";
var bulletColor4 = "#00a6ff";
var bulletColor5 = "#a100ff";

//----------------------------------------------------------------------------------//
//----------------------------------- variables ------------------------------------//
//----------------------------------------------------------------------------------//

var selectedTurretButton;
var selectedTurret;
var activeTurrets = [];
var activeBullets = [];
var enemy;
var enemyPos = 0;
var enemyActive = false;
var interval = null;

//----------------------------------------------------------------------------------//
//-------------------------- variables used to find path ---------------------------//
//----------------------------------------------------------------------------------//

var start = [(mapHeight-1)/2, 0];
var end = [(mapHeight-1)/2, mapWidth-1];
var path;

//----------------------------------------------------------------------------------//
//---------------------------- matrix used to find path ----------------------------//
//----------------------------------------------------------------------------------//

var matrix = [];

for (var i = 0; i < mapHeight; i++)
{
    matrix.push(new Array(0));
}

var col = 0;

for (var m = 0; m < matrix.length; m++)
{
    col = m;

    for (var n = 0; n < mapWidth; n++)
    {
        matrix[col].push(0);
    }
}

//----------------------------------------------------------------------------------//
//---------------------------- call find path function -----------------------------//
//----------------------------------------------------------------------------------//

path = findPath(start, end);

//----------------------------------------------------------------------------------//
//------------------------- find shortest path for enemies -------------------------//
//----------------------------------------------------------------------------------//

function findPath(position, end)
{
    var temp = [];

    for (var a = 0; a < matrix.length; a++)
    {
        temp[a] = matrix[a].slice();
    }

    var queue = [];
    temp[position[0]][position[1]] = 1;
    queue.push([position]);

    while (queue.length > 0)
    {
        var path = queue.shift();
        var pos = path[path.length-1];

        var direction =
        [
            [pos[0]+1, pos[1]],
            [pos[0], pos[1]+1],
            [pos[0]-1, pos[1]],
            [pos[0], pos[1]-1]
        ];

        for (var d = 0; d < direction.length; d++)
        {
            if (direction[d][0] == end[0] && direction[d][1] == end[1])
            {
                return path.concat([end]); 
            }

            if (direction[d][0]<0 || direction[d][0]>=temp.length || direction[d][1]<0 || direction[d][1]>=temp[0].length || temp[direction[d][0]][direction[d][1]]!=0)
            { 
                continue;
            }

            temp[direction[d][0]][direction[d][1]] = 1;
            queue.push(path.concat([direction[d]])); 
        }
    }
}

//----------------------------------------------------------------------------------//
//---------------------------- create map and interface ----------------------------//
//----------------------------------------------------------------------------------//

function createMap()
{
    // create borders

    for (var b = 0; b < 6; b++)
    {
        var border = document.createElement("div");
        border.style.position = "absolute";
        border.style.backgroundColor = borderColor;

        if (b < 2) // top, bottom borders
        {
            border.style.width = tileWidth*(mapWidth+1) + "px";
            border.style.height = tileHeight/2 + "px";
        }
        else // two left, two right borders
        {
            border.style.width = tileWidth/2 + "px";
            border.style.height = tileHeight*(mapHeight/2) + "px";
        }

        if (b > 3) // two right borders
        {
            border.style.left = tileWidth*(mapWidth+1) + "px";
        }
        else // top, bottom, two left borders
        {
            border.style.left = tileWidth/2 + "px";
        }

        if (b == 1) // bottom border
        {
            border.style.top = tileHeight*(mapHeight+1) + "px";
        }
        else if (b%2) // left-bottom, right-bottom borders
        {
            border.style.top = tileHeight*((mapHeight+3)/2) + "px";
        }
        else // top, left-top, right-top borders
        {
            border.style.top = tileHeight/2 + "px";
        }

        document.body.appendChild(border);
    }

    // create grid tiles

    for (var w = 0; w < mapWidth; w++)
    {
        for (var h = 0; h < mapHeight; h++)
        {
            var tile = document.createElement("div");
            tile.style.width = tileWidth + "px";
            tile.style.height = tileHeight + "px";
            tile.style.position = "absolute";
            tile.style.left = tileWidth*(w+1) + "px";
            tile.style.top = tileHeight*(h+1) + "px";

            if (w%2 && h%2 || w%2 == 0 && h%2 == 0)
            {
                tile.style.backgroundColor = gridColor1;
            }
            else 
            {
                tile.style.backgroundColor = gridColor2;
            }

            tile.setAttribute("xPos", w); // used to update the matrix when tile is filled with turret
            tile.setAttribute("yPos", h); // used to update the matrix when tile is filled with turret
            tile.setAttribute("filled", "false"); // used when placing turrets on tiles
            tile.addEventListener("mouseover", tileMouseOver);
            tile.addEventListener("click", tileClick);
                document.body.appendChild(tile);
        }
    }

    // create turret buttons

    for (var t = 1; t < 6; t++)
    {
        var turretButton = document.createElement("div");
        turretButton.style.width = tileWidth-2 + "px";
        turretButton.style.height = tileHeight-2 + "px";
        turretButton.style.position = "absolute";
        turretButton.style.left = tileWidth*(mapWidth+t+1) + "px";
        turretButton.style.top = tileHeight*2+1 + "px";
        turretButton.style.backgroundColor = turretTileColor;
        turretButton.setAttribute("id", "turretButton"+String(t)); // used when clicking on turret button thats already selected
        turretButton.setAttribute("bulletColor", window["bulletColor"+String(t)]); // used when creating bullets
        turretButton.addEventListener("click", turretButtonClick);
        var img = document.createElement("img");
        img.src = "images/turret-"+String(t)+".png";
        img.style.width = imageWidth + "px";
        img.style.height = imageHeight + "px";
        img.style.position = "relative";
        img.style.left = (tileWidth-2-imageWidth)/2 + "px";
        img.style.top = (tileHeight-2-imageHeight)/2 + "px";
        turretButton.appendChild(img);
        document.body.appendChild(turretButton);
    }

    // create start button

    var startButton = document.createElement("div");
    startButton.style.width = tileWidth*5-2 + "px";
    startButton.style.position = "absolute";
    startButton.style.left = tileWidth*(mapWidth+2) + "px";
    startButton.style.top = tileHeight/2 + "px";
    startButton.style.backgroundColor = borderColor;
    startButton.innerHTML = "START";
    startButton.style.padding = "9px 0 10px 0";
    startButton.style.color = textColor;
    startButton.style.textAlign = "center";
    startButton.style.cursor = "default";
    startButton.id = "startButton";
    startButton.addEventListener("click", startButtonClick);
    document.body.appendChild(startButton);
}

//----------------------------------------------------------------------------------//
//----------------------------- placing turrets on map -----------------------------//
//----------------------------------------------------------------------------------//

function turretButtonClick()
{
    if (selectedTurret == null) // if no turret selected
    {
        // select turret button

        selectedTurretButton = this;
        selectedTurretButton.style.outline = "2px solid " + textColor;

        // create copy of turret to be placed on map

        selectedTurret = this.cloneNode(true);
        selectedTurret.style.pointerEvents = "none";
        document.body.appendChild(selectedTurret);
    }
    else if (this.getAttribute("id") == selectedTurret.getAttribute("id")) // if turret button already selected
    {
        // deselect turret button

        this.style.outline = "none";
        document.body.removeChild(selectedTurret);
        selectedTurretButton = null;
        selectedTurret = null;
    }
}

function tileMouseOver()
{
    if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
    {
        // move turret to this tile

        selectedTurret.style.left = parseInt(this.style.left)+1 + "px";
        selectedTurret.style.top = parseInt(this.style.top)+1 + "px";
    }
}

function tileClick()
{
    if (selectedTurret && this.getAttribute("filled") == "false") // if turret selected and tile empty
    {
        // make turret active, deselect turret and turret button

        activeTurrets.push(selectedTurret);
        selectedTurretButton.style.outline = "none";
        selectedTurretButton = null;
        selectedTurret.setAttribute("fireCount", 0);
        selectedTurret.style.outline = "none";
        selectedTurret = null;

        // set tile to filled, update matrix and enemy path

        this.setAttribute("filled", "true");
        matrix[this.getAttribute("yPos")][this.getAttribute("xPos")] = 1;
        path = findPath(path[enemyPos], end);
        enemyPos = 0;
    }
}

//----------------------------------------------------------------------------------//
//------------------------ on start button click, add enemy ------------------------//
//----------------------------------------------------------------------------------//

function startButtonClick()
{
    // disable start button

    this.style.pointerEvents = "none";
    this.style.opacity = 0.5;

    // create enemy

    enemy = document.createElement("img");
    enemy.src = "images/enemy.png";
    enemy.style.width = imageWidth + "px";
    enemy.style.height = imageHeight + "px";
    enemy.style.position = "absolute";
    enemy.style.left = (tileWidth-imageWidth)/2 + "px";
    enemy.style.top = tileHeight*(mapHeight/2+1)-imageHeight/2 + "px";
    enemy.style.pointerEvents = "none";
    document.body.appendChild(enemy);
    enemyActive = true;
    path = findPath(start, end); // find enemy path
    interval = setInterval(function(){moveStuff();}, 10); // set interval for moving enemies and bullets
}

//----------------------------------------------------------------------------------//
//---------------------------- move enemies and bullets ----------------------------//
//----------------------------------------------------------------------------------//

function moveStuff()
{
    var currentX = parseInt(enemy.style.left);
    var currentY = parseInt(enemy.style.top);

    // move enemy along path

    if (enemyPos < path.length) // if enemy has not completed path
    {
        var targetX = tileWidth*(path[enemyPos][1]+1)+(tileWidth-imageWidth)/2;
        var targetY = tileHeight*(path[enemyPos][0]+1)+(tileHeight-imageHeight)/2;

        if (currentX < targetX)
        {
            currentX++;
            enemy.style.left = currentX + "px";
        }
        else if (currentX > targetX)
        {
            currentX--;
            enemy.style.left = currentX + "px";
        }
        else if (currentY < targetY)
        {
            currentY++;
            enemy.style.top = currentY + "px";
        }
        else if (currentY > targetY)
        {
            currentY--;
            enemy.style.top = currentY + "px";
        }
        
        if (currentX == targetX && currentY == targetY) // if enemy reached target tile
        {
            enemyPos++; // set enemy to next position in path
        }
    }
    else // enemy completed path
    {
        if (currentX == tileWidth*(mapWidth+1)+(tileWidth-imageWidth)/2) // if enemy reached final off-map tile
        {
            // remove enemy and interval

            enemyActive = false;
            clearInterval(interval);
            document.body.removeChild(enemy);
            enemyPos = 0;

            // reset start button

            document.getElementById("startButton").style.pointerEvents = "auto";
            document.getElementById("startButton").style.opacity = 1;
        }
        else // enemy has not reached final off-map tile
        {
            // move enemy to the right

            currentX++;
            enemy.style.left = currentX + "px";
        }
    }

    // create bullets

    for (var s = 0; s < activeTurrets.length; s++)
    {
        var turret = activeTurrets[s];
        var turretX = parseInt(turret.style.left);
        var turretY = parseInt(turret.style.top);
        var turretWidth = parseInt(turret.style.width);
        var turretHeight = parseInt(turret.style.height);
        var turretFireCount = parseInt(turret.getAttribute("fireCount"))

        // if enemy active and fire count is 0 and enemy in range of turret

        if (enemyActive && turretFireCount==0 && Math.abs(turretX-currentX)<turretRange && Math.abs(turretY-currentY)<turretRange)
        {
            // create bullet

            var bullet = document.createElement("div");
            bullet.style.width = bulletSize + "px";
            bullet.style.height = bulletSize + "px";
            bullet.style.position = "absolute";
            bullet.style.left = turretX+turretWidth/2-bulletSize/2 + "px";
            bullet.style.top = turretY+turretHeight/2-bulletSize/2 + "px";
            bullet.style.backgroundColor = turret.getAttribute("bulletColor");
            bullet.style.pointerEvents = "none";
            activeBullets.push(bullet);
            document.body.appendChild(bullet);
        }

        if (turretFireCount == fireRate) // if fire count equals fire rate
        {
            // reset fire count to 0

            turret.setAttribute("fireCount", 0);
        }
        else // fire count does not equal fire rate
        {
            // increase fire count by 1

            turret.setAttribute("fireCount", turretFireCount+1);
        }
    }

    // move bullets

    for (var u = 0; u < activeBullets.length; u++)
    {
        var currentBullet = activeBullets[u];
        var bulletX = parseInt(currentBullet.style.left);
        var bulletY = parseInt(currentBullet.style.top);
        var enemyX = parseInt(enemy.style.left) + parseInt(enemy.style.width)/2;
        var enemyY = parseInt(enemy.style.top) + parseInt(enemy.style.height)/2;

        // if enemy not active or bullet hit target

        if (enemyActive == "false" || Math.abs(bulletX-enemyX) < hitRange && Math.abs(bulletY-enemyY) < hitRange)
        {
            // remove bullet

            document.body.removeChild(currentBullet);
            activeBullets.splice(u);
        }
        else // enemy is active and bullet has not hit target
        {
            // move bullet

            var differenceX = enemyX-bulletX;
            var differenceY = enemyY-bulletY;
            var distance = Math.sqrt(differenceX*differenceX + differenceY*differenceY);

            if (distance <= bulletSpeed) // if distance between bullet and enemy less than or equal to bullet speed
            {
                // move bullet directly to enemy

                currentBullet.style.left = enemyX + 'px';
                currentBullet.style.top = enemyY + 'px';
            }
            else
            {
                // move bullet towards enemy

                var moveX = differenceX/distance*bulletSpeed;
                var moveY = differenceY/distance*bulletSpeed;
                currentBullet.style.left = (bulletX+moveX) + 'px';
                currentBullet.style.top = (bulletY+moveY) + 'px';
            }
        }
    }
}

//----------------------------------------------------------------------------------//
//--------------------------- on window load, create map ---------------------------//
//----------------------------------------------------------------------------------//

window.onload = function()
{
    createMap();
}
<html>
<head>
    <title>Tower Defense</title>

    <style>
        body
        {
            background-color: #000000;
            font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
            font-weight: bold;
            font-size: 14px;
        }
    </style>

    <script type="text/javascript" src="script.js"></script>
</head>
<body>
</body>
</html>

Query string in Javascript using Coun(), null, and >greater than

I have been trying to figure out how to use a query in javascript within NetSuite to get an item count. I can pull the information using SuiteQL in NetSuite but that syntax does not work in javascript. Here is what I’m trying to do.

var invoiceItemCntResult = query.runSuiteQL({
    query: 'select Count(transactionline.transaction) as ItemCnt
    from transaction
    left join transactionline
    on transaction.id=transactionline.transaction
    WHERE custcol_cp_year > 0
    and custcol_cp_carrierid !== null
    and transaction.externalID = ?', params: [externalID]});
              
var invoiceItemCnt = invoiceItemCntResult.asMappedResults();       
var invoiceItemCntValue = invoiceItemCnt.length > 0 ? invoiceItemCnt[0].ItemCnt : null;

//Log ItemCnt
log.debug({
title: '1a. ItemCnt',
details: JSON.stringify(invoiceItemCntValue)});

I’ve tried wrapping null in single quotes and double quotes as well.
I’ve tried dropping “as ItemCnt” and using Count(transactionline.transaction)

If I remove the first two pieces of the where clause and just look for the
externalID it runs but doesn’t return a value for invoiceItemCntValue.

I’ve been working on various things for a couple hours and I’m not sure
why I’m not seeing it, seems like it should be fairly simple.

Forcing user to deal with dialogue while performing validation and await in a loop?

When the user clicks the button at the bottom of the table to add a new item, the dialogue is built in function GetDialogue and displayed and the script awaits the value of the button clicked.

Are there any issue with resetting the promise in the dialogue and awaiting in the while loop until the user provides a valid input or cancels?

This example tests the input for a match in the table’s Value column for a hardcode Type of T2.

It appears to work, but could it leave unfulfilled promises to accumulate or is there a more correct/reliable/safe way to do this?

Thank you.

"use strict";
let 
   dialogue = document.querySelector('.dialogue'),
   inputElem = dialogue.querySelector('input'),
   tbody = document.querySelector('table tbody'),
   promiseObj = Object.create(null, {
      resolve: {
         value: null,
         writable: true
      },
      reject: {
         value: null,
         writable: true
      }
   })
;
document.querySelector('.newItem').addEventListener(
   'mousedown',
   async (evt) => {
      if (dialogue.classList.contains('show')) return;
      let dupName, msgResp, items, inputValue, type="t2";
      items = tbody.querySelectorAll(`tr > td:nth-child(2).${type} + td`);
      msgResp = await GetDialogue();
      if (msgResp === "2" ) {
        CloseDialogue();
        return;
      }
      dupName = 1;
      while (dupName) {
        dupName = 0;
        inputValue = inputElem.value.trim();
        for (let e of items) { 
          if (e.textContent === inputValue) {
            dupName=1;
            break;
          }
        }
        if (dupName) {
          alert("Duplicate name");
          let msgResp = await ResetDialogue();
          if ( msgResp === "2" ) {
            CloseDialogue();
            return;
          }
        }
      }
      CloseDialogue();
   },
   false
);
dialogue.firstElementChild.lastElementChild.addEventListener(
  'mousedown',
  (evt) => {
     let e = evt.target;
     if ( !e.matches('button, button *') || !e.closest('button')) return;
     promiseObj.resolve(e.value);
  },
  false
);
function GetDialogue() {
  inputElem.value = "";
  dialogue.classList.add('show');
  return new Promise( (resolve, reject) => {
    promiseObj.resolve = resolve;
    promiseObj.reject = reject;
  });  
}  
function CloseDialogue() {
  dialogue.classList.remove('show');  
}
function ResetDialogue() {
  return new Promise( (resolve, reject) => {
    promiseObj.resolve = resolve;
    promiseObj.reject = reject;
  });  
}
* { box-sizing: border-box; }
.dialogue {
  display:none;
  position: fixed;
  width: 100vw;
  height: 100vh;
  top: 0;
  left: 0;
  background-color: rgba(100,100,100,0.8);
}
.dialogue.show {
  display: block;
}
.msgbox {
  position: absolute;
  top: 50px;
  right: 0;
  border: 1px solid black;
  background-color: rgb(200,220,220);
  width: fit-content;
  padding: 15px;
}
p {
  padding:0;
  margin: 0;
}
input {
  margin:10px 0;
  width: 100%;
}
.btnbox {
  display: flex;
  justify-content: space-around;
}
<table>
  <thead><tr><th>Key</th><th>Type</th><th>Value</th></thead>
  <tbody>
     <tr><td>1</td><td class="t1">T1</td><td>a</td></tr>
     <tr><td>2</td><td class="t1">T1</td><td>b</td></tr>
     <tr><td>3</td><td class="t1">T1</td><td>c</td></tr>
     <tr><td>4</td><td class="t1">T1</td><td>d</td></tr>
     <tr><td>5</td><td class="t2">T2</td><td>A</td></tr>
     <tr><td>6</td><td class="t2">T2</td><td>B</td></tr>
     <tr><td>7</td><td class="t2">T2</td><td>C</td></tr>
     <tr><td>8</td><td class="t2">T2</td><td>D</td></tr>
  </tbody>
</table>
<button class="newItem">Add new item</button>
<div class="dialogue">
  <div class="msgbox">
    <p>Input new value:</p>
    <input type="text">
    <div class="btnbox">
      <button value="1">Continue</button><button value="2">Cancel</button>
    </div>
  </div>
</div>

How to create a HUD-like overlay in a CesiumJS canvas (non-html)?

I want to draw a HUD overlay to a CesiumJS (webgl) canvas that contains some rapidly changing data (such as current flight speed of an airplane).

I can do that by adding a Cesium Billboard with my custom canvas. However, if the canvas of the billboard changes rapidly, the Billbaord will first flicker, and if the data changes even faster, it will disappear completely. Does anyone understand why, and how can I fix this?

Here’s a minmal example. Move the world around and see the flicker: Sandcastle

const viewer = new Cesium.Viewer("cesiumContainer");

const hudImageProperty = new Cesium.ConstantProperty();
const hudPositionProperty = new Cesium.ConstantPositionProperty();

let hudDisplayValue = 0;

setInterval(() => {
  // simulating rapid change of the desired display value
  // the faster we change the value, the more it will flicker / disappear completely
  const canvas = Cesium.writeTextToCanvas(hudDisplayValue.toString(),{font: "bold 64px sans-serif"});
  hudImageProperty.setValue(canvas);
  hudDisplayValue++; 
}, 100); 

const hud = viewer.entities.add({
  position: hudPositionProperty,
  billboard: {
    image: hudImageProperty,
    horizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
    disableDepthTestDistance: Number.POSITIVE_INFINITY,
    scale: 1.0,
  },
});

viewer.scene.preRender.addEventListener((scene) => { 
  // some complicated computation to make sure the billboard is displayed on the top right of the display
  const screenPosition = new Cesium.Cartesian2(200, 100);
  const ray = scene.camera.getPickRay(screenPosition);
  const direction = Cesium.Cartesian3.normalize(ray.direction, new Cesium.Cartesian3());
  const hudOffset = Cesium.Cartesian3.multiplyByScalar(direction, 1000, new Cesium.Cartesian3());
  const hudPosition = Cesium.Cartesian3.add(scene.camera.positionWC, hudOffset, new Cesium.Cartesian3());
  hudPositionProperty.setValue(hudPosition); 
});

I don’t want to use HTML for the overlay, since I want to be able to record a video from the CesiumJS canvas, so it needs to be drawn directly in the canvas.

Focused state of a button is not rendered on button.focus() called from a click handler

Demonstration of the problem

   const elementSet = {
        input: document.querySelector("input"),
        buttonToFocusOn: document.querySelector("button:first-of-type"),
        buttonFocusOnInput: document.querySelector("section button:first-of-type"),
        buttonFocusOnButton: document.querySelector("section button:last-of-type"),
    };

    elementSet.buttonFocusOnInput.onclick = () => elementSet.input.focus();
    elementSet.buttonFocusOnButton.onclick = () => elementSet.buttonToFocusOn.focus();
        <input value="element to focus on"/>
        <button>Button to focus on</button>
        <section>
            <button>Focus on input</button>
            <button>Focus on first button</button>
        </section>

Please understand that I cut many corners in this code in order to demonstrate the problem is the simplest way.

Description of the problem

Element’s function focus() works in all cases, it can be easily tested. However, the focuses state of the focuses element is not always rendered. The problem appears when I click on another button, and its click handler focuses another button, only when the element to focus is a button, and only for the mouse click. If the button is activated by the keyboard, everything works correctly.

I reproduced the problem using different engines: Blink+V8 (Chromium, Google Chrome, Vivaldi, Opera (only for Windows)) and Mozilla Quantum+Gecko (Firefox).

Steps to reproduce

  1. Click on the button "Focus on input" or activate it using the keyboard. In all cases, the input element is activated and the effect of focusing is manifested correctly.

  2. Click the button "Focus on first button" with a mouse. It focuses the button "Button to focus on". To confirm it, press on the spacebar. It may or may not reveal the focus state of the focused button. Alternatively, hit the TAB; as expected, it will move focus to the next control, that is, to the button "Focus on input".

    The problem is: immediately after the click on "Focus on first button", right button is focused, but visually, the focus state is not properly rendered.

  3. Navigate to the button "Focus on first button" using the keyboard and press the spacebar. That is, call the handler of this button without touching a mouse. In this case, everything works correctly: the button "Button to focus on" is not only focuses, but the focus state is rendered correctly.

What have I tried?

The first thing I tried was:

Instead of

elementSet.buttonFocusOnButton.onclick =
    () => elementSet.buttonToFocusOn.focus();

using

elementSet.buttonFocusOnButton.onclick =
    () => setTimeout(() => elementSet.buttonToFocusOn.focus());
// optionally, with some non-zero delay

It does now change anything, the lack of focus state rendering still takes place.

The attempt to style the button to be focused can eliminate the problem, depending on what it is, but it does not look like a radical solution.

Why programmatic focusing is important?

The real prototype for this code fragment was the demo application for one of my components published in 2015, it attracted a lot of attention and very positive feedback. At that time, I missed the problem. The component, as many other components, is shown temporarily. So, in many applications, after the use of the component, it is very important to focus on some other element, to make the user’s workflow most suggestive, depending on the prior user’s choice.

My questions:

  1. It looks to me like a bug in both layout engines. Is it really a bug and not a feature?

  2. Is there a radical comprehensive solution for this problem?

  3. If the radical comprehensive solution is impossible due to the bug, what can we use as the best workaround technique?

Thank you!

How can i build TailwindCSS from a HTML string

How can i build TailwindCSS from a HTML string?

I tried do that:

import postcss from 'postcss'
import tailwindcss from '@tailwindcss/postcss'

export async function buildTailwind(content, css = '') {
  const inputCSS = `
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    ${css}
  `

  const result = await postcss([
    tailwindcss({
      content: [{ raw: content, extension: 'html' }]
    })
  ]).process(inputCSS, { from: undefined })

  return result.css
}

await buildTailwind('<p class="text-red-500">Hello, World!</p>')

But, unsuccessful, i recieved that output:

/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */

Remember, i want to build a TailwindCSS from a HTML string, preferably without using npx or cli stuff.

Google Custom Search: add a default thumbnail for pdf file results via Javascript?

I’m using Google Custom Search (the hosted version, not the API version), and am using the callback method https://developers.google.com/custom-search/docs/more_examples to customize the CSS of the results.

But I see an issue with thumbnail images for pdf files; basically, there are no thumbnail images for pdfs in my search results.

enter image description here


My question is: how can I add a default thumbnail image for pdf files in my search results?

I see three possibilities:

  1. use a “if pdf file” in Javascript in the callback to determine if the result is a pdf and then load a default image for all pdfs.

  2. add a pdf file type somehow in the Page Map and add a default thumbnail for pdfs: https://support.google.com/programmable-search/answer/1626955

  3. Use jQuery after the search resuts load to add a thumbnail to pdf search results?

The first possibility for a default thumbnail is within the callback. I’m currently using this to modify the CSS of results:

SearchResultsRenderedCallback = function(){
document.getElementsByClassName("gsc-cursor")[0].style.fontSize = '20px';
// more 
}
};
window.__gcse || (window.__gcse = {});
window.__gcse.searchCallbacks = {
  web: {
      rendered: SearchResultsRenderedCallback,
  },
};

Is it possible to use a conditional, i.e. “if pdf file”, in Javascript in the callback to determine if the result is a pdf and then load a default image for all pdfs?

The second possibility is add a pdf file type somehow in the Page Map and add a default thumbnail for pdfs: https://support.google.com/programmable-search/answer/1626955

In the header of my site, I added a conditional to determine if a post had a thumbnail, and if so, use that for <meta name=”thumbnail” in the Page Map, and if no thumbnail existed, use a default image. This works fine and all posts have a thumbnail in search results, either the exisiing thumbnail or a default generic thumbnail.

if (has_post_thumbnail( $post->ID )) {
$image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'thumbnail' );
echo '<meta name="thumbnail" content="' . $image[0] . '" />';
} else {
echo '<meta name="thumbnail" content="'. get_template_directory_uri() . '/img/placeholderimage.jpg" />';
};

How can I add thumbnail for pdfs in a Page Map. Or is that possible?

The third possibility is to use Javascript or jQuery after the search results (not in the callback) to add a default image to each search result that is a pdf file. Is this possible? Or would this conflict with the overall search results as loaded by callback?

Need a line thin font for a THREE.js effect

I want to implement a special THREE js effect using fonts. Basically it involves clustering points around a line. For this I need a font made uf of lines like this:

enter image description here

But all the fonts THREEjs ships with unfortunately have volume:

enter image description here

Making them unsuitable for the effect. So how can I ideally implement thin text that covers at least most latin fonts?

Using SharedArrayBuffer from a SharedWorker?

In JavaScript, is it possible to use a SharedArrayBuffer in a SharedWorker?

When serving an index.html

<html>
 <body>
  <script type="text/javascript">
   console.log(
    'outer:', 'typeof SharedArrayBuffer:', typeof SharedArrayBuffer,
    'crossOriginIsolated:', crossOriginIsolated);
   new SharedWorker('worker.js');
  </script>
 </body>
</html>

and a worker.js

console.log(
 'inner:', 'typeof SharedArrayBuffer:', typeof SharedArrayBuffer,
 'crossOriginIsolated:', crossOriginIsolated);

with e.g. Emscripten‘s emrun (which sends

Access-Control-Allow-Origin: *
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: credentialless
Cross-Origin-Resource-Policy: cross-origin

headers among others; whichever of these may or may not be relevant here), browsing that index.html with e.g. Chrome 138.0.7204.168 on Linux, the index.html tab’s console says

outer: typeof SharedArrayBuffer: function crossOriginIsolated: true

as expected, but the shared worker’s console (see chrome://inspect/#workers) says

inner: typeof SharedArrayBuffer: undefined crossOriginIsolated: false

A very wild guess for a reason might be https://html.spec.whatwg.org/multipage/workers.html#run-a-worker in step 12.3.4 saying

If worker global scope’s embedder policy‘s value is compatible with cross-origin isolation and is shared is true, then set agent’s agent cluster‘s cross-origin isolation mode to “logical” or “concrete“. The one chosen is implementation-defined.

and in step 12.3.6 saying

Set worker global scope’s cross-origin isolated capability to true if agent’s agent cluster‘s cross-origin isolation mode is “concrete”.

so it might be Chrome decides to set that to “logical“—as per the note at https://html.spec.whatwg.org/multipage/document-sequences.html#cross-origin-isolation-mode:

On some platforms, it is difficult to provide the security properties required to grant safe access to the APIs gated by the [https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-cross-origin-isolated-capability](cross-origin isolated capability]. As a result, only “concrete” can grant access that capability. “logical” is used on platform not supporting this capability, where various restrictions imposed by cross-origin isolation will still apply, but the capability is not granted.

(And thus the security requirements for using SharedArrayBuffer would not be met.)

(SharedArrayBuffer and SharedWorker looks related, but it is from 2017 and I don’t know how relevant that question and its answer are still today.)

Getting Blade components as elements inside Javascript without using identifiers or parents

function()
const PARSER = new DOMParser();

@php
ob_start();
@endphp

<x-item-label for="text-input" :value="__('Random text')" />

@php
$itemLabel = ob_get_clean();
@endphp

const ITEM_LABEL = PARSER.parseFromString(`{!! addcslashes($itemLabel, '`') !!}`, 'text/html').body.firstChild;

// other code
}

This is a modified .blade.php file from a test Laravel project that I would use in views using the view()->render() function.

I wanted to see if I can use blade components and get them into variables for further use without having to rely on unique ids or similar.

I am not well-versed in these frameworks/languages, so my question is this a sensible way to go about it (aka would you see something like this in production)? Or should I be using other methods/packages/frameworks?

Voice Wake Word Not Working on Mobile Browsers Using SpeechRecognition in React

I’m building a React web app that uses the Web Speech API (SpeechRecognition) to detect a wake word (like “Hey Wiz”) from the user’s microphone input.
The functionality works perfectly in desktop web browsers like Chrome — I get the transcript, and the wake word is detected as expected.
However, on mobile browsers (Chrome on Android and Safari on iOS), even though:

  • Microphone permission is granted,

  • No critical errors are thrown,

  • The logs show that SpeechRecognition started successfully,

  • …the onresult event never fires, and no voice input is captured.

    **LOGS
    **
    Speech recognition language set to: en-US  
    Starting speech recognition…  
    Wake word listener started successfully  
    Speech recognition error: aborted  
    Speech recognition aborted  
    Speech recognition ended  
    Speech recognition started successfully

const startWakeWordListener = (wakeWord: string, onWakeDetected: () => void) => {
    // Check browser compatibility
    const SpeechRecognition =
      window.SpeechRecognition ||
      window.webkitSpeechRecognition ||
      (window as any).msSpeechRecognition;

    if (!SpeechRecognition) {
      alert("Speech recognition is not supported in this browser");
      return;
    }

    // Request microphone permission with better error handling
    const requestMicrophoneAccess = async () => {
      try {
        console.log("Requesting microphone access...");
        // First try with basic constraints
        const basicConstraints = { audio: true };
        console.log("Trying basic audio constraints:", basicConstraints);
        const stream = await navigator.mediaDevices.getUserMedia(basicConstraints);
        mediaStream.current = stream;
        console.log("Microphone access granted successfully with basic constraints");
        // Now enumerate devices to see what we got
        const devices = await navigator.mediaDevices.enumerateDevices();
        const audioDevices = devices.filter(device => device.kind === 'audioinput');
        console.log("Available audio devices after permission:", audioDevices);

        return true;
      } catch (error: any) {
        console.error("Basic microphone access failed:", error);
        // Try with more specific constraints
        try {
          const specificConstraints = {
            audio: {
              echoCancellation: true,
              noiseSuppression: true,
              autoGainControl: true,
              sampleRate: 16000
            }
          };
          console.log("Trying specific audio constraints:", specificConstraints);
          const stream = await navigator.mediaDevices.getUserMedia(specificConstraints);
          mediaStream.current = stream;
          console.log("Microphone access granted successfully with specific constraints");
          return true;
        } catch (specificError: any) {
          console.error("Specific microphone access also failed:", specificError);

          // Handle specific error types
          if (specificError.name === 'NotFoundError' || specificError.name === 'DevicesNotFoundError') {
            console.error("No microphone device found");
            alert("No microphone device found. Please connect a microphone and refresh the page.");
          } else if (specificError.name === 'NotAllowedError' || specificError.name === 'PermissionDeniedError') {
            console.error("Microphone permission denied");
            alert("Microphone permission denied. Please allow microphone access in your browser settings and refresh the page.");
          } else if (specificError.name === 'NotReadableError' || specificError.name === 'TrackStartError') {
            console.error("Microphone is already in use");
            alert("Microphone is already in use by another application. Please close other applications using the microphone and try again.");
          } else if (specificError.name === 'OverconstrainedError' || specificError.name === 'ConstraintNotSatisfiedError') {
            console.error("Microphone constraints not satisfied");
            alert("Microphone constraints not satisfied. Please check your microphone settings.");
          } else {
            console.error("Unknown microphone error:", specificError);
            alert(Microphone error: ${specificError.message || 'Unknown error'}. Please check your microphone settings and try again.);
          }
          return false;
        }
      }
    };

    // Start the wake word listener process
    const initializeWakeWordListener = async () => {
      console.log("Initializing wake word listener...");
      const hasAccess = await requestMicrophoneAccess();
      if (!hasAccess) {
        console.error("Failed to get microphone access");
        return;
      }

      try {
        console.log("Creating speech recognition instance...");
        const recognitionInstance = new SpeechRecognition();
        recognitionInstance.continuous = true;
        recognitionInstance.interimResults = true;

        const languageCode = selectedLanguage.replace("_", "-");
        recognitionInstance.lang = languageCode;
        console.log("Speech recognition language set to:", languageCode);

        accumulatedTranscript.current = "";

        recognitionInstance.onsoundstart = () => {
          console.log("Sound detected - wake word listener is active");
          setIsNotValid(false);
        };

        recognitionInstance.onsoundend = () => {
          console.log("Sound ended");
        };

        recognitionInstance.onstart = () => {
          console.log("Speech recognition started successfully");
          setIsWakeWordListenerActive(true);
        };

        recognitionInstance.onresult = (event: any) => {
          let fullTranscript = "";

          for (let i = event.resultIndex; i < event.results.length; i++) {
            const transcript = event.results[i][0].transcript;
            fullTranscript += transcript + " ";
          }

          console.log('Wake word listener transcript:', fullTranscript);


          const cleanTranscript = fullTranscript.replace(/s+/g, " ").trim().toLowerCase();
          console.log('Clean transcript:', cleanTranscript, 'Looking for:', wakeWord.toLowerCase());

          if (cleanTranscript.includes(wakeWord.toLowerCase())) {
            console.log(Wake word "${wakeWord}" detected!);
            recognitionInstance.stop();
            onWakeDetected(); // ⬅️ Call your bot/show logic here
          }
        };

        recognitionInstance.onend = () => {
          console.log("Speech recognition ended456");
          setIsWakeWordListenerActive(false);
          if (isRecording) {
            try {
              console.log("Restarting speech recognition...");
              recognitionInstance.start(); // Auto-restart
            } catch (e) {
              console.warn("Could not restart recognition:", e);
            }
          }
        };

        recognitionInstance.onerror = (event: any) => {
          console.error("Speech recognition error:", event.error);

          // Handle specific speech recognition errors
          if (event.error === 'no-speech') {
            console.log("No speech detected, continuing to listen...");
          } else if (event.error === 'audio-capture') {
            console.error("Audio capture error - microphone may be unavailable");
            alert("Audio capture error. Please check your microphone connection and try again.");
          } else if (event.error === 'network') {
            console.error("Network error in speech recognition");
            alert("Network error in speech recognition. Please check your internet connection.");
          } else if (event.error === 'not-allowed') {
            console.error("Speech recognition not allowed");
            alert("Speech recognition not allowed. Please check your browser permissions.");
          } else if (event.error === 'aborted') {
            console.log("Speech recognition aborted");
          } else {
            console.error("Unknown speech recognition error:", event.error);
          }
        };

        recognition.current = recognitionInstance;
        console.log("Starting speech recognition...");
        recognition.current.start();
        console.log("Wake word listener started successfully");
      } catch (error) {
        console.error("Error initializing wake word recognition:", error);
        alert("Error initializing wake word recognition. Please refresh the page and try again.");
      }
    };

    // Start the initialization process
    initializeWakeWordListener();

    // Add a timeout to check if wake word listener is working
    setTimeout(() => {
      if (!isWakeWordListenerActive) {
        console.warn("Wake word listener may not be working properly. Check microphone permissions and browser settings.");
      }
    }, 5000);
  };

Flexbox and rotation

I’m currently struggling with Flexbox and image rotation.

In the following snippet, I have a list of images that are all the same size, although their dimensions are not known in advance. Some of the images may be randomly rotated by 90 degrees.

I want the container to automatically adjust when one or more images are rotated, so they don’t overlap each other.

.container {
    display: flex;
    height: 200px;
    width: 600px;
    background-color: yellow;
    justify-content: center;
    align-items: center;
}

.item {
    justify-content: center;
    align-items: center;
}

.rotated-item {
}

img {
    height: 150px;
}

.rotated-img {
    transform: rotate(90deg);
}
<div class="container">
    <div class="item">
        <img src="https://www.deckofcardsapi.com/static/img/2D.png" />
    </div>
    <div class="item rotated-item">
        <img class="rotated-img" src="https://www.deckofcardsapi.com/static/img/6C.png" />
    </div>
    <div class="item">
        <img src="https://www.deckofcardsapi.com/static/img/7D.png" />
    </div>
</div>