feat(demo): add story 1 — Sorano: Rock and Time

This commit is contained in:
2026-06-20 21:19:57 +02:00
parent 42ed59a6b3
commit 8f87155c1d
5508 changed files with 1595740 additions and 124 deletions
+150
View File
@@ -0,0 +1,150 @@
(function () {
'use strict';
/**
* Set window.CAP_CUSTOM_WASM_URL from any cap container's data attribute
* so the vendored WASM binary is used instead of the default jsDelivr CDN.
* Safe to call multiple times; noop after first assignment.
*/
function ensureWasmUrl(root) {
if (window.CAP_CUSTOM_WASM_URL) return;
const scope = root || document;
const c = scope.querySelector('[data-captcha-provider="cap"][data-cap-wasm-url]');
if (c) window.CAP_CUSTOM_WASM_URL = c.dataset.capWasmUrl;
}
/**
* Wire up a single invisible-mode Cap container:
* - starts a speculative background solve
* - intercepts the enclosing form's submit to wait for the token
* - exposes __capReset / __capWired on the container so we can
* skip double-wiring and re-arm after an XHR submit.
*
* Safe to call repeatedly: returns early if the container is already wired.
*/
function wireInvisibleContainer(container) {
if (!container || container.__capWired) return;
if (typeof window.Cap !== 'function') {
// cap.min.js hasn't loaded yet — try again shortly.
setTimeout(() => wireInvisibleContainer(container), 50);
return;
}
const form = container.closest('form') || document.getElementById(container.dataset.formId);
if (!form) return;
const tokenInput = container.querySelector('input[name="cap-token"]');
if (!tokenInput) return;
const endpoint = container.dataset.capApiEndpoint || '/forms-cap/';
container.__capWired = true;
const cap = new window.Cap({ apiEndpoint: endpoint });
let solvePromise = null;
let verified = false;
const startSolve = () => {
verified = false;
tokenInput.value = '';
solvePromise = cap.solve()
.then((r) => { tokenInput.value = r.token; return r; })
.catch((err) => { console.error('[cap] solve failed', err); throw err; });
};
startSolve();
container.__capReset = () => {
try { cap.reset(); } catch (e) { /* ignore */ }
startSolve();
};
form.addEventListener('submit', async (event) => {
if (verified && tokenInput.value) return;
event.preventDefault();
event.stopImmediatePropagation();
const submitter = event.submitter || null;
try {
await solvePromise;
} catch (e) {
return;
}
verified = true;
// Defer: HTMLFormElement.requestSubmit() is a no-op while
// the form's "firing submission events" flag is still set,
// i.e. while we're still inside the original submit handler.
setTimeout(() => {
if (submitter) {
form.requestSubmit(submitter);
} else {
form.requestSubmit();
}
}, 0);
}, true);
}
/**
* Scan the document (or a specific root) for any invisible Cap containers
* that haven't been wired yet and wire them up.
*/
function wireAllInvisible(root) {
const scope = root || document;
const containers = scope.querySelectorAll(
'[data-captcha-provider="cap"][data-cap-mode="invisible"]'
);
containers.forEach(wireInvisibleContainer);
}
function registerXhrHandler() {
if (!window.GravFormXHR || !window.GravFormXHR.captcha) return false;
window.GravFormXHR.captcha.register('cap', {
reset: function (container, form) {
const capContainer = (container && container.matches('[data-captcha-provider="cap"]'))
? container
: form.querySelector('[data-captcha-provider="cap"]');
if (!capContainer || !capContainer.isConnected) return;
const mode = capContainer.dataset.capMode || 'invisible';
if (mode === 'invisible') {
// After an XHR form re-render, the container is usually a
// brand-new element — wire it up from scratch. If it's the
// same element we already wired, re-arm in place.
ensureWasmUrl(form);
if (capContainer.__capWired && typeof capContainer.__capReset === 'function') {
capContainer.__capReset();
} else {
wireInvisibleContainer(capContainer);
}
return;
}
// Checkbox mode: reset the <cap-widget> if it's solved.
const widget = capContainer.querySelector('cap-widget');
if (!widget || !widget.isConnected || !widget.token) return;
try { widget.reset(); } catch (e) { console.error('Error resetting Cap widget:', e); }
const tokenInput = form.querySelector('input[name="cap-token"]');
if (tokenInput) tokenInput.value = '';
}
});
return true;
}
function init() {
ensureWasmUrl(document);
wireAllInvisible(document);
registerXhrHandler();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// Expose for manual re-wiring (e.g., when a form is dynamically inserted).
window.GravCapCaptcha = window.GravCapCaptcha || {};
window.GravCapCaptcha.wireAll = wireAllInvisible;
window.GravCapCaptcha.wire = wireInvisibleContainer;
})();