getreadystack's picture
Upload index.html with huggingface_hub
ce94b43 verified
Raw History Blame Contribute Delete
40.2 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Cookie Consent prior Consent rule — check your HTML free</title>
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/consent-gate-html-lint"></head>
<body>
<div class="wrap">
<h1>Cookie Consent prior Consent rule — check your HTML free</h1>
<p class="lede">Finds third-party script and iframe tags in your HTML that load trackers before the visitor consents — the prior-consent rule of EU/UK ePrivacy. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">&lt;!doctype html&gt;
&lt;html lang="de"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;Nordlicht Physiotherapie — Termin buchen&lt;/title&gt;
&lt;link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&amp;display=swap" rel="stylesheet"&gt;
&lt;script async src="https://www.googletagmanager.com/gtag/js?id=G-4KDX2QP7ZL"&gt;&lt;/script&gt;
&lt;script&gt;
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-4KDX2QP7ZL');
&lt;/script&gt;
&lt;script&gt;
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window,document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '921470553318842'); fbq('track', 'PageView');
&lt;/script&gt;
&lt;script src="https://static.hotjar.com/c/hotjar-3841205.js?sv=6" async&gt;&lt;/script&gt;
&lt;link rel="stylesheet" href="/assets/site.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Physiotherapie in Kiel&lt;/h1&gt;
&lt;p&gt;Termine Montag bis Freitag, 8 bis 18 Uhr.&lt;/p&gt;
&lt;h2&gt;So finden Sie uns&lt;/h2&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/aqz-KE-bpKQ"
title="Praxisrundgang" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;Termin anfragen&lt;/h2&gt;
&lt;form action="/termin" method="post"&gt;
&lt;label&gt;Name &lt;input name="name" required&gt;&lt;/label&gt;
&lt;label&gt;E-Mail &lt;input name="email" type="email" required&gt;&lt;/label&gt;
&lt;button class="g-recaptcha" data-sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"&gt;Anfrage senden&lt;/button&gt;
&lt;/form&gt;
&lt;script src="https://www.google.com/recaptcha/api.js" async defer&gt;&lt;/script&gt;
&lt;footer&gt;&lt;a href="/datenschutz"&gt;Datenschutz&lt;/a&gt; &amp;middot; &lt;a href="/impressum"&gt;Impressum&lt;/a&gt;&lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Check the HTML file open in your editor and name every tag that loads before consent. <strong>In the editor:</strong> Sweep every HTML file in the workspace and write a dated, per-file evidence report you keep — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var CG_RULES = {
"version": "1.0.0",
"scope": "EU/EEA (ePrivacy Directive 2002/58/EC Art. 5(3) as implemented nationally) and the UK (PECR reg. 6)",
"gate_markers": "type\\s*=\\s*[\"']text/plain[\"']|data-(cookieconsent|cookiecategory|cookieblock|category|consent|cmp[a-z-]*|klaro|borlabs|cmplz|usercentrics|iub)\\b|class\\s*=\\s*[\"'][^\"']*(cmplz-|klaro|optanon)",
"cmp_loaders": "cookiebot|klaro|usercentrics|onetrust|optanon|cookieyes|cookie-?law-?info|complianz|cmplz|borlabs|osano|termly|iubenda|axeptio|didomi|tarteaucitron|cookieconsent",
"rules": [
{
"id": "analytics-before-consent",
"sev": "error",
"kind": "tag",
"pattern": "googletagmanager\\.com/(gtag/js|gtm\\.js)|google-analytics\\.com/(analytics|ga)\\.js|gtag\\.js",
"title": "Google Analytics / Tag Manager loads before consent",
"why": "ePrivacy Art. 5(3) / UK PECR reg. 6 require consent BEFORE the _ga cookie is written; GA4 writes it on load.",
"fix": "type=\"text/plain\" data-category=\"analytics\" and let your CMP unblock it after opt-in"
},
{
"id": "ad-pixel-before-consent",
"sev": "error",
"kind": "tag",
"pattern": "connect\\.facebook\\.net/[^\"']*fbevents\\.js|analytics\\.tiktok\\.com|static\\.ads-twitter\\.com|snap\\.licdn\\.com|sc-static\\.net/scevent|s\\.pinimg\\.com/ct",
"title": "Advertising pixel fires before consent",
"why": "Marketing pixels are never 'strictly necessary', so no consent means no lawful basis under Art. 5(3).",
"fix": "type=\"text/plain\" data-category=\"marketing\" and load it from the CMP callback"
},
{
"id": "ad-pixel-inline-snippet",
"sev": "error",
"kind": "line",
"pattern": "^(?!.*\u003cscript)(?!.*\\ssrc\\s*=).*(connect\\.facebook\\.net/[^\\\"'\\s]*fbevents\\.js|analytics\\.tiktok\\.com|static\\.ads-twitter\\.com/uwt\\.js|sc-static\\.net/scevent\\.min\\.js|snap\\.licdn\\.com/li\\.lms-analytics)",
"title": "Inline pixel snippet injects an ad tracker at parse time",
"why": "The copy-paste Meta/TikTok snippet builds its own \u003cscript\u003e element, so blocking by tag attribute never sees it.",
"fix": "Wrap the whole snippet in your CMP's accepted callback, or park it as type=\"text/plain\" data-category=\"marketing\""
},
{
"id": "google-fonts-hotlinked",
"sev": "error",
"kind": "tag",
"pattern": "fonts\\.googleapis\\.com|fonts\\.gstatic\\.com",
"title": "Google Fonts hotlinked — the visitor's IP goes to Google",
"why": "LG Munchen I, 3 O 17493/20 (20 Jan 2022) awarded a visitor EUR 100 for exactly this embed.",
"fix": "Self-host the .woff2 files and serve them from your own origin"
},
{
"id": "youtube-embed-not-nocookie",
"sev": "error",
"kind": "tag",
"pattern": "(youtube\\.com|youtu\\.be)/(embed|watch)",
"title": "YouTube embed uses the tracking domain",
"why": "youtube.com/embed drops VISITOR_INFO1_LIVE on render; the nocookie host defers it.",
"fix": "Swap the host for youtube-nocookie.com, or click-to-load behind consent"
},
{
"id": "recaptcha-before-consent",
"sev": "error",
"kind": "tag",
"pattern": "google\\.com/recaptcha/api\\.js|hcaptcha\\.com/1/api\\.js",
"title": "CAPTCHA script loads on page view, not on form use",
"why": "It is only necessary while the form is being submitted, not while the page is merely read.",
"fix": "Inject the script on first focus of the form, or gate it as data-category=\"functional\""
},
{
"id": "session-replay-before-consent",
"sev": "error",
"kind": "tag",
"pattern": "static\\.hotjar\\.com|clarity\\.ms|fullstory\\.com/s/fs\\.js|smartlook|mouseflow|luckyorange",
"title": "Session recorder starts before consent",
"why": "Replay tools capture keystrokes and can sweep in special-category data, so regulators treat them as high risk.",
"fix": "Start the recorder only from the CMP's accepted callback"
},
{
"id": "maps-iframe-before-consent",
"sev": "error",
"kind": "tag",
"pattern": "google\\.com/maps/embed|maps\\.googleapis\\.com",
"title": "Google Maps iframe loads before consent",
"why": "The iframe contacts Google and transfers the IP address on page load, same as the Fonts case.",
"fix": "Show a static image placeholder and load the iframe on click, after consent"
},
{
"id": "tracker-preconnect",
"sev": "warn",
"kind": "tag",
"pattern": "rel\\s*=\\s*[\"'](preconnect|dns-prefetch|preload)[\"'][^\u003e]*(google-analytics|googletagmanager|facebook\\.net|doubleclick|hotjar|fonts\\.gstatic)",
"title": "preconnect opens a tracker connection before consent",
"why": "A preconnect completes a TLS handshake, so the visitor's IP reaches the tracker even if the script never runs.",
"fix": "Delete the hint, or add it from JS after consent is stored"
},
{
"id": "inline-cookie-write",
"sev": "error",
"kind": "line",
"pattern": "document\\.cookie\\s*=\\s*[\"'`][^\"'`]*(_ga|_gid|_fbp|_fbc|utm_|visitor|tracking)",
"title": "Inline script writes a tracking cookie",
"why": "Art. 5(3) covers any storage on the device, not just third-party scripts.",
"fix": "Move the write inside the consent callback"
},
{
"id": "inline-storage-write",
"sev": "warn",
"kind": "line",
"pattern": "(localStorage|sessionStorage)\\.setItem\\(\\s*[\"'`][^\"'`]*(utm|campaign|visitor|clientId|client_id|_ga|fbclid|gclid)",
"title": "Inline script stores a campaign identifier",
"why": "localStorage is 'information stored in the terminal equipment' under Art. 5(3), exactly like a cookie.",
"fix": "Write it only after the marketing category is accepted"
},
{
"id": "consent-attr-typo",
"sev": "warn",
"kind": "tag",
"pattern": "type\\s*=\\s*[\"']text/plain[\"'](?![^\u003e]*data-)",
"title": "Script is parked as text/plain but carries no category attribute",
"why": "No CMP can ever unblock it, so the tag is dead rather than gated.",
"fix": "Add the data-category / data-cookieconsent attribute your CMP expects",
"on_gated": true
},
{
"id": "no-cmp-present",
"sev": "error",
"kind": "doc",
"title": "Trackers are present but no consent platform loads on this page",
"why": "Without a CMP nothing can ever be blocked or unblocked, so every tracker above runs unconditionally.",
"fix": "Add a CMP (Klaro, Cookiebot, Complianz, OneTrust...) and gate every tag listed here"
},
{
"id": "cmp-after-tracker",
"sev": "error",
"kind": "doc",
"title": "The consent banner loads after a tracker that is already running",
"why": "HTML executes top-down: a tag above the CMP has already fired by the time the banner is drawn.",
"fix": "Move the CMP loader above every third-party tag in \u003chead\u003e"
},
{
"id": "gtag-consent-default-missing",
"sev": "error",
"kind": "doc",
"title": "Google tag present without a Consent Mode v2 default state",
"why": "Since March 2024 Google requires ad_storage / analytics_storage / ad_user_data / ad_personalization defaults for EEA traffic.",
"fix": "Call gtag('consent','default',{ad_storage:'denied',analytics_storage:'denied',ad_user_data:'denied',ad_personalization:'denied'}) before the tag loads"
}
]
}
;</script>
<script>// Consent Gate HTML Lint — one brain, used by the VS Code extension and by the free web page.
'use strict';
var DATA = (typeof module !== 'undefined') ? require('./rules.json') : window.CG_RULES;
var RULES = DATA.rules;
var GATE = new RegExp(DATA.gate_markers, 'i');
var CMP = new RegExp(DATA.cmp_loaders, 'i');
var TAG = /<(script|link|iframe|img)\b[^>]*>/gi;
var TRACKER_IDS = ['analytics-before-consent', 'ad-pixel-before-consent', 'google-fonts-hotlinked',
'youtube-embed-not-nocookie', 'recaptcha-before-consent', 'session-replay-before-consent',
'maps-iframe-before-consent'];
function lineOf(text, idx) { return text.slice(0, idx).split('\n').length; }
function msgOf(r) { return r.title + ' — ' + r.why + ' Fix: ' + r.fix; }
function check(text, opts) {
text = String(text || '');
opts = opts || {};
var findings = [], trackerLines = [], m, i, r;
var lines = text.split('\n');
// 1. every <script>/<link>/<iframe> tag, gated or not
var parked = {};
TAG.lastIndex = 0;
while ((m = TAG.exec(text)) !== null) {
var tag = m[0], ln = lineOf(text, m.index), gated = GATE.test(tag);
if (gated && /^<script/i.test(tag)) {
var endIdx = text.toLowerCase().indexOf('<\/script>', m.index);
var endLn = endIdx < 0 ? lineOf(text, m.index + tag.length) : lineOf(text, endIdx);
for (var k = ln; k <= endLn; k++) parked[k] = true;
}
for (i = 0; i < RULES.length; i++) {
r = RULES[i];
if (r.kind !== 'tag') continue;
if (gated !== !!r.on_gated) continue;
if (r.id === 'youtube-embed-not-nocookie' && /youtube-nocookie\.com/i.test(tag)) continue;
if (new RegExp(r.pattern, 'i').test(tag)) {
findings.push({ check: r.id, sev: r.sev, msg: msgOf(r), line: ln });
if (TRACKER_IDS.indexOf(r.id) >= 0) trackerLines.push(ln);
}
}
}
// 2. inline JS, skipped while inside a parked (text/plain) script block
for (i = 0; i < lines.length; i++) {
if (parked[i + 1]) continue;
for (var j = 0; j < RULES.length; j++) {
r = RULES[j];
if (r.kind !== 'line') continue;
if (new RegExp(r.pattern, 'i').test(lines[i])) {
findings.push({ check: r.id, sev: r.sev, msg: msgOf(r), line: i + 1 });
if (r.id === 'ad-pixel-inline-snippet') trackerLines.push(i + 1);
}
}
}
// 3. page-level rules
var cmpLine = 0;
for (i = 0; i < lines.length; i++) { if (CMP.test(lines[i])) { cmpLine = i + 1; break; } }
var byId = {};
for (i = 0; i < RULES.length; i++) byId[RULES[i].id] = RULES[i];
if (trackerLines.length && !cmpLine) {
findings.push({ check: 'no-cmp-present', sev: 'error', msg: msgOf(byId['no-cmp-present']), line: trackerLines[0] });
}
var early = trackerLines.filter(function (l) { return cmpLine && l < cmpLine; });
if (early.length) {
findings.push({ check: 'cmp-after-tracker', sev: 'error', msg: msgOf(byId['cmp-after-tracker']), line: cmpLine });
}
var hasGoogleTag = /googletagmanager\.com|google-analytics\.com|gtag\s*\(/i.test(text);
var hasDefault = /gtag\s*\(\s*['"]consent['"]\s*,\s*['"]default['"]/i.test(text);
if (hasGoogleTag && !hasDefault) {
var gl = 1;
for (i = 0; i < lines.length; i++) { if (/googletagmanager\.com|google-analytics\.com|gtag\s*\(/i.test(lines[i])) { gl = i + 1; break; } }
findings.push({ check: 'gtag-consent-default-missing', sev: 'error', msg: msgOf(byId['gtag-consent-default-missing']), line: gl });
}
findings.sort(function (a, b) { return (a.line || 0) - (b.line || 0); });
return { findings: findings, checked: RULES.length, today: opts.today || '' };
}
var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.CGENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var CGREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = CGREPORT;
else window.CGREPORT = CGREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.CGENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.CGREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.consent-gate-html-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-gate-html-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.consent-gate-html-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind Consent Gate HTML Lint (EU/UK ePrivacy) every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"consent-gate-html-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Names every script and iframe in your HTML that loads a tracker before the visitor has consented.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A German court ordered EUR 100 in damages to a single visitor over one hotlinked Google Fonts request (LG Munchen I, 3 O 17493/20, 20 January 2022); the same breach under GDPR Art. 83(5) is exposed to fines of up to EUR 20 million</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_n1yAdUns90LigSNtiTVNHsN94ZnF5xmr1YbQx0YT726" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Consent Gate HTML Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads your HTML and names every third-party script, iframe and link tag that contacts a tracker before the visitor has consented. Fifteen rules cover Google Analytics, Meta and TikTok pixels, hotlinked Google Fonts, YouTube embeds, reCAPTCHA, session recorders, Maps frames, inline cookie writes and missing Consent Mode defaults.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Freelance web developers and small agencies who build landing pages, WordPress themes and static sites for clients in the EU, the EEA and the UK. It is aimed at the person who writes the markup, not at the lawyer who reviews it afterwards, and it runs on the file open in the editor.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why are free tools not enough for this?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Browser devtools and Lighthouse list every request a page makes, but they never say which of those requests needs prior consent under ePrivacy Article 5(3). Cookie scanners only see a deployed URL. This reads the source before you deploy and cites the rule and the fix for each tag.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Checking the file open in your editor is free forever, with all fifteen rules, line numbers, statutes and fixes; the free web page does the same for pasted markup. A licence adds the workspace sweep across every HTML file at once and the dated per-file report you keep.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does the same check cost otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A German court ordered EUR 100 in damages to one visitor over a single hotlinked Google Fonts request, in LG Munchen I, 3 O 17493/20 of 20 January 2022. Under GDPR Article 83(5) the exposure reaches EUR 20 million or 4% of worldwide annual turnover. The licence is 29 dollars once.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Consent Gate HTML Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your HTML and names every third-party script, iframe and link tag that contacts a tracker before the visitor has consented. Fifteen rules cover Google Analytics, Meta and TikTok pixels, hotlinked Google Fonts, YouTube embeds, reCAPTCHA, session recorders, Maps frames, inline cookie writes and missing Consent Mode defaults."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Freelance web developers and small agencies who build landing pages, WordPress themes and static sites for clients in the EU, the EEA and the UK. It is aimed at the person who writes the markup, not at the lawyer who reviews it afterwards, and it runs on the file open in the editor."}}, {"@type": "Question", "name": "Why are free tools not enough for this?", "acceptedAnswer": {"@type": "Answer", "text": "Browser devtools and Lighthouse list every request a page makes, but they never say which of those requests needs prior consent under ePrivacy Article 5(3). Cookie scanners only see a deployed URL. This reads the source before you deploy and cites the rule and the fix for each tag."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Checking the file open in your editor is free forever, with all fifteen rules, line numbers, statutes and fixes; the free web page does the same for pasted markup. A licence adds the workspace sweep across every HTML file at once and the dated per-file report you keep."}}, {"@type": "Question", "name": "What does the same check cost otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A German court ordered EUR 100 in damages to one visitor over a single hotlinked Google Fonts request, in LG Munchen I, 3 O 17493/20 of 20 January 2022. Under GDPR Article 83(5) the exposure reaches EUR 20 million or 4% of worldwide annual turnover. The licence is 29 dollars once."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:0,slug:"consent-gate-html-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/about-page-template"><span class="yj-rel-t">About Page Template — 6 Finished About Pages for Small Service Businesses (Sel</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/contact-page-template"><span class="yj-rel-t">Contact Page Template — 20 Ready-to-Upload Contact Pages for Small Service Bus</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/landing-page-template"><span class="yj-rel-t">Landing Page Template — 8 Ready-Written Pages for an Online Course Launch (Pla</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/portfolio-website-template"><span class="yj-rel-t">Portfolio Website Template — Freelance Designer Portfolio &amp; Case Study Site (8</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/webflow-restaurant-template"><span class="yj-rel-t">Webflow Restaurant Template — delivered as self-contained HTML you own: 26 pag</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
</body>
</html>