bestcrosswords.com is my go-to online quick crosswords website. It hosts American-style 15x15 free-to-play puzzles at different difficulty levels. According to them, they’re “the largest supplier of free crossword puzzles on the web, publishing 15 grids daily from an archive of more 100,000”. That’s awesome! You know what’s not awesome? Asking for a payment to fullscreen a crossword twice.

Thank you for providing such good puzzles for free, but no thank you to this specific request. This clearly seems like a flag they’d want to store per-device. Because accounts aren’t involved yet, the only two reasonable methods are cookies and localStorage. I happened to check the right one first.

Devtools console showing localStorage containing a single key named "com.bestcrosswords.applet.solvable.SolvableCrosswordPuzzleApplication/settings/setFullScreen".

Hehe. Presumably a timestamp of when the user last used fullscreen. So if the key is no longer there, like the first load, no more blockers?

delete localStorage["com.bestcrosswords.applet.solvable.SolvableCrosswordPuzzleApplication/settings/setFullScreen"];

Yes. No more blockers. Only until the next time you use fullscreen though, of course.

Multiple ways to fix that. Adding a button to delete the timestamp is the simplest and easiest, but we can do better. Possibly made using GWT (pronounced gwit), the page’s event handler code is obfuscated. Luckily though, the fullscreen “button” contains a single click event listener. And it’s returned by the element’s onclick property.

Devtools inspector showing the fullscreen "button" containing a single "click" event handler.

This makes things easy. A simple hack would be to just overwrite onclick with a function that deletes the timestamp and calls the original handler.

const button = document.querySelector("#gwt_casualinteractive div.bc-fullscreen-button");
const originalHandler = button.onclick;
button.onclick = function() {
    delete localStorage["com.bestcrosswords.applet.solvable.SolvableCrosswordPuzzleApplication/settings/setFullScreen"];
    originalHandler.apply(this, arguments);
}

Sweet! We know have infinite fullscreen toggle-ability.

Slightly unfortunately though, this hack cannot be run on window load, as the inner game DOM is loaded separately. Multiple ways to fix that too. Considering the simplicity of the situation, a timed loop to check the existence of the button is good enough; no need for more sophisticated solutions.

A sample user script that checks for the button every two seconds, for a maximum of ten times:

// ==UserScript==
// @name         BestCrosswords Fullscreen De-limiter
// @version      1.0
// @description  Removes the paywall for more-than-once fullscreen toggle.
// @namespace    https://krove.space/
// @author       Krove
// @match        https://www.bestcrosswords.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bestcrosswords.com
// ==/UserScript==

(function() {
    'use strict';

    const checkIntervalMs = 2000;
    const maxCheckCount = 10;
    let currentCheckCount = 0;

    function getFullscreenButton() {
        return document.querySelector("#gwt_casualinteractive div.bc-fullscreen-button");
    }

    function patchFullscreenButton(button) {
        const originalHandler = button.onclick;
        button.onclick = function() {
            delete localStorage["com.bestcrosswords.applet.solvable.SolvableCrosswordPuzzleApplication/settings/setFullScreen"];
            originalHandler.apply(this, arguments);
        }
    }

    function patchOrSchedule() {
        if (++currentCheckCount > maxCheckCount) {
            return;
        }

        const button = getFullscreenButton();
        if (button === null) {
            setTimeout(patchOrSchedule, checkIntervalMs);
        } else {
            patchFullscreenButton(button);
        }
    }

    window.addEventListener('load', patchOrSchedule);
})();