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
Pending
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.