Prepare any webpage for clean interaction by detecting and removing disruptive overlays (cookie banners, GDPR consent, modals, popups, newsletter signups, paywalls, login walls). Uses a cached database of 300+ known CMPs (Consent-O-Matic + EasyList) combined with heuristic DOM scanning. Produces portable JS recipes for any browser tool (Playwright, CDP, cmux-browser). ALWAYS use this skill before taking screenshots, scraping content, or automating interaction on any webpage that might have overlays blocking the view or preventing interaction. Triggers on: page prep, clean page, remove overlays, dismiss cookie banner, page blocked, overlay cleanup, consent banner, prepare page, unblock page, clear popups, cookie popup.
100
100%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Detect and remove overlays (cookie banners, GDPR consent, modals, paywalls, login walls) before screenshots, scraping, or browser automation. Node 22+ required. No npm dependencies.
if [[ -n "${CLAUDE_SKILL_DIR:-}" ]]; then
PAGE_PREP_DIR="${CLAUDE_SKILL_DIR}/scripts"
else
PAGE_PREP_DIR="$(dirname "$(command -v overlay-db.js 2>/dev/null || \
find ~/.claude -path "*/page-prep/scripts/overlay-db.js" -type f 2>/dev/null | head -1)")"
fiStore in PAGE_PREP_DIR and prefix all commands below with
node "$PAGE_PREP_DIR/overlay-db.js".
Resolve PAGE_PREP_DIR using the block above. Verify the path is non-empty
before continuing.
node "$PAGE_PREP_DIR/overlay-db.js" refreshDownloads and merges Consent-O-Matic rules + EasyList cookie filters into a
local cache (~/.cache/page-prep/). Skips network fetch if cache is less than
7 days old. Run with --force to bypass the age check.
BUNDLE="$(node "$PAGE_PREP_DIR/overlay-db.js" bundle)"Captures a self-contained JS string (no imports, no external deps) to stdout. The bundled script embeds the full CMP database and heuristic scanner.
Evaluate $BUNDLE in the active page using whichever browser tool is in use
(see Browser Tool Examples). The script runs synchronously and returns a
detection report.
The injection return value is a JSON detection report. Parse it to enumerate
detected overlays. Each overlay has a source field: "cmp-match" (database
match) or "heuristic" (DOM scan).
source: "cmp-match"): the report includes a complete dismiss
recipe with ordered steps. Use it directly.source: "heuristic", dismiss: null): compose a dismiss
sequence yourself — try Escape key, then close buttons, then element removal
(see Agent Fallback).Combine hide and dismiss recipes for all detected overlays into a single
manifest (see Recipe Manifest Format). Include the global scroll_fix if
scroll_locked is true.
hide.js block in one
browser_evaluate call. Hides all overlays and restores scroll.dismiss.steps entry
sequentially using the browser tool's click/key primitives. Use this when
the site requires a real consent signal (analytics, A/B tests).The detection script catches known CMPs and common heuristic patterns, but it will miss overlays that don't fit those signals — third-party login prompts (Google One Tap, Apple Sign In), custom-built modals, iframes, or elements injected after the initial scan. Accessibility tree snapshots also miss iframes and elements outside the main document tree.
Run this check to find remaining blockers:
JSON.stringify([...document.querySelectorAll('*')].filter(el => {
var s = getComputedStyle(el);
return s.position === 'fixed' && parseInt(s.zIndex, 10) > 1000
&& (el.offsetWidth > 100 || el.offsetHeight > 100);
}).map(el => {
var s = getComputedStyle(el);
return { tag: el.tagName, id: el.id, cls: (el.className || '').slice(0, 50),
z: s.zIndex, w: el.offsetWidth, h: el.offsetHeight };
}))Evaluate this via the browser tool. It returns all visible position:fixed
elements with z-index > 1000 and non-trivial dimensions. Ignore
legitimate elements (navigation bars, toolbars) and remove the rest:
document.querySelector('<selector>')?.remove().This verification loop is the agent's value over the heuristic script alone — the script handles the 80% of known patterns fast, the agent handles the 20% that requires judgment.
For multi-step sessions where new overlays may appear (SPAs, lazy-loaded banners), inject the watch mode snippet after cleanup (see Watch Mode).
// Inject and capture report
const report = await browser_evaluate({
expression: BUNDLE // the string captured from `bundle`
});node "$CDP_JS" eval "$(node "$PAGE_PREP_DIR/overlay-db.js" bundle)"cmux browser --surface <ref> eval "$(node "$PAGE_PREP_DIR/overlay-db.js" bundle)"{
"overlays": [
{
"id": "overlay-0",
"type": "cookie-consent",
"source": "cmp-match", // "cmp-match" | "heuristic"
"cmp": "cookiebot", // CMP name (only for cmp-match)
"selector": "#CybotCookiebotDialog",
"confidence": 1.0,
"hide": ["#CybotCookiebotDialog { display:none!important }"],
"dismiss": [{ "action": "click", "selector": "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll" }]
},
{
"id": "overlay-1",
"type": "unknown-modal",
"source": "heuristic",
"selector": "div.gdpr-wall",
"confidence": 0.45,
"signals": ["high-z-index", "keyword-match", "scroll-lock-boost"],
"hide": ["div.gdpr-wall { display:none!important }"],
"dismiss": null // agent composes dismiss (see Agent Fallback)
}
],
"scroll_locked": true,
"scroll_fix": "html,body { overflow:auto!important; height:auto!important }"
}{
"overlays": [
{
"id": "cookiebot",
"hide": {
"css": ["#CybotCookiebotDialog { display: none !important; }"],
"js": "document.querySelector('#CybotCookiebotDialog')?.remove()"
},
"dismiss": {
"steps": [
{ "action": "click", "selector": "#CybotCookiebotDialogBodyButtonAccept" }
],
"js": "/* composed from steps */"
}
}
],
"scroll_fix": "document.body.style.overflow=''"
}When dismiss is null, attempt in order:
[aria-label*="close" i], [aria-label*="dismiss" i], .close,
button:has(svg), button[class*="close"].document.querySelector('<selector>')?.remove().Consult known patterns for CMP-specific dismiss patterns when the above three steps fail.
Inject after cleanup for pages that load overlays dynamically.
window.__pagePrep = (() => {
let timer = null;
let pending = [];
const MODE = 'hide'; // 'hide' | 'dismiss'
function scan() {
// Re-run heuristic scanner on current DOM
const found = window.__pagePrepScan?.() ?? [];
if (found.length === 0) return;
if (MODE === 'hide') {
found.forEach(o => {
const el = document.querySelector(o.selector);
if (el) el.style.display = 'none';
});
} else {
// 'dismiss' mode — queue for agent
found.forEach(o => {
if (!pending.find(p => p.id === o.id)) pending.push(o);
});
}
}
const observer = new MutationObserver(() => {
clearTimeout(timer);
timer = setTimeout(scan, 500);
});
observer.observe(document.body, { childList: true, subtree: true });
return {
watch: () => observer.observe(document.body, { childList: true, subtree: true }),
stop: () => { observer.disconnect(); clearTimeout(timer); },
pending: () => [...pending],
};
})();window.__pagePrep.pending()
for the agent to process interactively.window.__pagePrep.stop() when the session is done.refresh --force if detection misses a known CMP — the database may be stale.node "$PAGE_PREP_DIR/overlay-db.js" status to check cache age and entry count.node "$PAGE_PREP_DIR/overlay-db.js" lookup <cmp-name> to check if a CMP is in
the database before injecting.