Download index.html from getreadystack/consent-mode-v2-lint: direct link, hf CLI and curl.
- Browser
- Download file 48.9 kB
-
https://huggingface.co/spaces/getreadystack/consent-mode-v2-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/consent-mode-v2-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/consent-mode-v2-lint/resolve/main/index.html
48.9 kB
| <html lang="en"><head><meta charset="utf-8"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static;justify-content:flex-end;margin:0 0 10px}h1,h2,h3,h4,p,li,dd,dt,blockquote,figcaption,summary,label,.res,.lead{overflow-wrap:anywhere}label{max-width:100%;box-sizing:border-box}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style> | |
| </head><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Consent Mode v2 Lint for GA4 & Google Ads</title> | |
| <meta name="description" content="Finds the consent-signal bugs that silently switch off Google Ads conversions and remarketing for EEA and UK traffic - in your source, before you ship."> | |
| <style> | |
| :root{color-scheme:light dark} | |
| body{margin:0;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif; | |
| background:#fbfbfa;color:#16161d} | |
| @media(prefers-color-scheme:dark){body{background:#14141a;color:#e8e8ea}} | |
| .wrap{max-width:880px;margin:0 auto;padding:28px 20px 64px} | |
| h1{font-size:1.7rem;line-height:1.25;margin:.2em 0 .3em} | |
| p.sub{margin:0 0 1.4em;opacity:.8} | |
| textarea{width:100%;min-height:230px;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace; | |
| padding:12px;border:1px solid #c9c9d1;border-radius:10px;background:transparent;color:inherit; | |
| box-sizing:border-box} | |
| button{font:600 15px/1 inherit;padding:12px 22px;border:0;border-radius:10px; | |
| background:#1d4ed8;color:#fff;cursor:pointer;margin:12px 0} | |
| button:hover{background:#1a43bd} | |
| table{border-collapse:collapse;width:100%;margin-top:14px;font-size:14px} | |
| th,td{border-bottom:1px solid #d8d8e0;padding:8px 6px;text-align:left;vertical-align:top} | |
| th{font-weight:600;opacity:.75} | |
| td.n{width:64px;font:13px ui-monospace,monospace;opacity:.7} | |
| .ok{padding:14px;border-radius:10px;background:#e8f5ec;color:#14532d;margin-top:14px} | |
| @media(prefers-color-scheme:dark){.ok{background:#15321f;color:#bbf7d0}} | |
| .next{margin-top:30px;padding:18px;border:1px solid #d8d8e0;border-radius:12px} | |
| .next h2{font-size:1.05rem;margin:0 0 .5em} | |
| .next ul{margin:.3em 0 1em 1.1em;padding:0} | |
| </style> | |
| <div class="wrap"> | |
| <h1>Consent Mode v2 Lint for GA4 & Google Ads</h1> | |
| <p class="sub">Finds the consent-signal bugs that silently switch off Google Ads conversions and remarketing for EEA and UK traffic - in your source, before you ship.</p> | |
| <textarea id="src" placeholder="Paste your gtag/GTM snippet, an HTML page, or a GTM container export (.json) here"></textarea> | |
| <button id="go">Check consent mode</button> | |
| <div id="out"></div> | |
| <div class="next"><h2>In the full version</h2><ul><li>Every file in the repository, not just the open one</li><li>A report file you can hand to a client or a DPO</li><li>Machine-readable output for CI</li><li><a href="https://getreadystack.com/api/buy/cl/polar_cl_ocwoiYU2se1AQ7U833HEc0yqBbp1nX4M5ylxf05WMYh">Get the full version - $29</a></li></ul></div> | |
| </div> | |
| <script> | |
| var RULES=[{"pattern": "\\b(adStorage|adUserData|adPersonalization|analyticsStorage|securityStorage|functionalityStorage|personalizationStorage)\\b", "flags": "", "sev": "error", "fix": "ad_user_data", "message": "Not a consent signal name. Consent types are snake_case (ad_storage, analytics_storage, ad_user_data, ad_personalization) - a camelCase key sets no consent state at all."}, {"pattern": "['\"](ad-storage|ad-user-data|ad-personalization|analytics-storage)['\"]", "flags": "", "sev": "error", "fix": "ad_user_data", "message": "Consent type names use underscores, not hyphens - this key sets no consent state."}, {"pattern": "\\b(analytic_storage|ads_storage|ad_userdata|ad_personalisation|ad_personalizations)\\b", "flags": "", "sev": "error", "fix": "ad_personalization", "message": "Misspelled consent type. The four required names are ad_storage, analytics_storage, ad_user_data, ad_personalization (US spelling, no 's' on ad_storage)."}, {"pattern": "['\"]?(ad_storage|analytics_storage|ad_user_data|ad_personalization|security_storage|functionality_storage|personalization_storage)['\"]?\\s*:\\s*(true|false)\\b", "flags": "", "sev": "error", "fix": "'denied'", "message": "Consent value must be the string 'granted' or 'denied'. A boolean is not a valid consent state."}, {"pattern": "['\"]?(ad_storage|analytics_storage|ad_user_data|ad_personalization)['\"]?\\s*:\\s*[01]\\s*[,}]", "flags": "", "sev": "error", "fix": "'denied'", "message": "Consent value must be the string 'granted' or 'denied', not a number."}, {"pattern": "['\"]?(ad_storage|analytics_storage|ad_user_data|ad_personalization)['\"]?\\s*:\\s*['\"](true|false)['\"]", "flags": "", "sev": "error", "fix": "'denied'", "message": "Consent value must be 'granted' or 'denied' - the strings 'true' and 'false' are not consent states."}, {"pattern": "['\"]?(ad_storage|analytics_storage|ad_user_data|ad_personalization)['\"]?\\s*:\\s*(granted|denied)\\s*[,}]", "flags": "", "sev": "error", "fix": "'denied'", "message": "Consent value is an unquoted identifier. It must be the quoted string 'granted' or 'denied'."}, {"pattern": "region\\s*:\\s*\\[[^\\]]*['\"](EU|EEA|EU27|EUROPE|EEA-UK|GDPR)['\"]", "flags": "i", "sev": "error", "fix": "region: ['DE','FR','ES','IT','NL','GB']", "message": "There is no 'EU' or 'EEA' shorthand. The region parameter takes ISO 3166-2 codes only (e.g. 'DE', 'FR', 'ES-MD'). An unrecognised code silently applies no regional default."}, {"pattern": "wait_for_update['\"]?\\s*:\\s*([0-9]|[1-9][0-9]|[1-4][0-9]{2})\\s*[,}]", "flags": "", "sev": "warn", "fix": "wait_for_update: 500", "message": "wait_for_update is under 500 ms. An asynchronous consent banner will not have answered yet, so the first hits fire with the default (denied) state."}, {"pattern": "wait_for_update['\"]?\\s*:\\s*['\"]", "flags": "", "sev": "error", "fix": "wait_for_update: 500", "message": "wait_for_update must be a number of milliseconds, not a string."}, {"pattern": "url_passthrough['\"]?\\s*:\\s*false", "flags": "", "sev": "warn", "fix": "url_passthrough: true", "message": "url_passthrough is off. While ad_storage is denied, the ad-click id is not carried in the URL, so conversions lose their campaign attribution."}, {"pattern": "ads_data_redaction['\"]?\\s*:\\s*false", "flags": "", "sev": "warn", "fix": "ads_data_redaction: true", "message": "ads_data_redaction is off. When ad_storage is denied, ad identifiers are still sent in full."}, {"pattern": "dataLayer\\s*\\.\\s*push\\s*\\(\\s*\\[\\s*['\"]consent['\"]", "flags": "", "sev": "error", "fix": "gtag('consent','default',{...})", "message": "Consent cannot be set with dataLayer.push([...]). It must use the gtag() arguments form so the value lands before the tag reads it."}, {"pattern": "gtag\\s*\\(\\s*['\"]consent['\"]\\s*,\\s*['\"](defaults|updates|set|Default|DEFAULT|Update|UPDATE)['\"]", "flags": "", "sev": "error", "fix": "gtag('consent','default',{...})", "message": "The consent command takes 'default' or 'update' - lowercase and singular. Any other value is ignored, so no consent state is ever set."}, {"pattern": "['\"]consent['\"]\\s*,\\s*['\"]default['\"][^;]*(ad_storage|ad_user_data|ad_personalization|analytics_storage)['\"]?\\s*:\\s*['\"]granted", "flags": "", "sev": "error", "fix": "'denied'", "message": "The consent default grants a signal before the visitor has answered. For EEA/UK traffic the default must be 'denied', raised by gtag('consent','update',...) once the banner is answered."}, {"pattern": "allow_google_signals", "flags": "", "sev": "info", "fix": "ad_storage", "message": "Google Signals no longer governs whether data reaches Google Ads. Since 15 June 2026 that is controlled by the ad_storage consent signal alone; this setting now only affects GA4 behavioural reporting."}, {"pattern": "(G-XXXXXXXXXX|AW-XXXXXXXXX|GTM-XXXXXXX|G-XXXXXXX\\b)", "flags": "", "sev": "error", "fix": "your real tag ID", "message": "Placeholder tag ID left in the snippet - no data reaches the property, and consent settings on it are never exercised."}, {"pattern": "anonymize_ip", "flags": "", "sev": "info", "fix": "(remove)", "message": "anonymize_ip has no effect in GA4 - IP anonymisation is always on. Leaving it in a privacy review implies a control you do not actually operate."}, {"json": {"kind": "each", "list": "containerVersion.tag", "paths": ["consentSettings.consentStatus"], "when": {"path": "exportFormatVersion"}}, "sev": "warn", "message": "GTM container export: tag has no consent settings, so it fires as soon as its trigger fires regardless of consent state"}, {"json": {"kind": "each_bad", "list": "containerVersion.tag", "path": "consentSettings.consentStatus", "bad": ["notneeded"], "filter": {"path": "type", "has": "awct"}, "when": {"path": "exportFormatVersion"}}, "sev": "error", "message": "GTM container export: Google Ads conversion tag is marked 'No additional consent required', so it fires before the visitor answers the banner"}]; | |
| // ★JSON 구조 검사 (s138) — ⛔줄 정규식이 ★못 보는 것을 본다: 문서 전체의 빠진 칸 · 목록 각 칸의 빠진 칸. | |
| // ★어휘 넷뿐이다: doc(문서에 이 칸이 있나) · ver(판 번호가 기준 이상인가) | |
| // each(목록의 각 칸에 이 칸이 있나) · each_bad(값이 쓸모없는 값인가) | |
| var JRULES = [{"json": {"kind": "each", "list": "containerVersion.tag", "paths": ["consentSettings.consentStatus"], "when": {"path": "exportFormatVersion"}}, "sev": "warn", "message": "GTM container export: tag has no consent settings, so it fires as soon as its trigger fires regardless of consent state"}, {"json": {"kind": "each_bad", "list": "containerVersion.tag", "path": "consentSettings.consentStatus", "bad": ["notneeded"], "filter": {"path": "type", "has": "awct"}, "when": {"path": "exportFormatVersion"}}, "sev": "error", "message": "GTM container export: Google Ads conversion tag is marked 'No additional consent required', so it fires before the visitor answers the banner"}]; | |
| function jHas(v) { | |
| if (v === null || v === undefined) return false; | |
| if (typeof v === 'string') return v.trim() !== ''; | |
| if (Array.isArray(v)) return v.length > 0; | |
| if (typeof v === 'object') return Object.keys(v).length > 0; | |
| return true; | |
| } | |
| function jVal(o, p) { | |
| var parts = String(p).split('.'), cur = o, i, k, got; | |
| for (i = 0; i < parts.length; i++) { | |
| if (cur === null || cur === undefined) return undefined; | |
| if (Array.isArray(cur)) { // ★목록을 만나면 ★남은 길을 각 칸에 물어본다 | |
| for (k = 0; k < cur.length; k++) { | |
| got = jVal(cur[k], parts.slice(i).join('.')); | |
| if (jHas(got)) return got; | |
| } | |
| return undefined; | |
| } | |
| if (typeof cur !== 'object') return undefined; | |
| cur = cur[parts[i]]; | |
| } | |
| return cur; | |
| } | |
| function jAny(o, paths) { | |
| for (var i = 0; i < (paths || []).length; i++) { if (jHas(jVal(o, paths[i]))) return true; } | |
| return false; | |
| } | |
| function jNum(s) { | |
| var m = String(s === undefined || s === null ? '' : s).match(/(\d+(?:\.\d+)*)/); | |
| return m ? m[1].split('.').map(Number) : null; | |
| } | |
| function jCmp(a, b) { | |
| for (var i = 0; i < Math.max(a.length, b.length); i++) { | |
| var x = a[i] || 0, y = b[i] || 0; | |
| if (x !== y) return x < y ? -1 : 1; | |
| } | |
| return 0; | |
| } | |
| function jWhen(doc, w) { | |
| if (!w) return true; | |
| var v = jVal(doc, w.path); | |
| if (w.eq !== undefined) return String(jHas(v) ? v : '').toLowerCase() === String(w.eq).toLowerCase(); | |
| if (w.has !== undefined) { | |
| var s = Array.isArray(v) ? v.join(' ') : String(jHas(v) ? v : ''); | |
| return s.toLowerCase().indexOf(String(w.has).toLowerCase()) >= 0; | |
| } | |
| return jHas(v); | |
| } | |
| function jList(doc, j) { | |
| var arr = jVal(doc, j.list); | |
| if (!Array.isArray(arr)) return []; | |
| if (!j.filter) return arr; | |
| return arr.filter(function (e) { | |
| var v = jVal(e, j.filter.path); | |
| var s = Array.isArray(v) ? v.join(' ') : String(jHas(v) ? v : ''); | |
| return s.toLowerCase().indexOf(String(j.filter.has).toLowerCase()) >= 0; | |
| }); | |
| } | |
| function jLine(raw, needle) { | |
| if (!needle) return 1; | |
| var s = String(raw), i = s.indexOf(JSON.stringify(String(needle))); | |
| if (i < 0) i = s.indexOf(String(needle)); | |
| if (i < 0) return 1; | |
| return s.slice(0, i).split(/\r?\n/).length; | |
| } | |
| function jName(e) { | |
| if (!e || typeof e !== 'object') return ''; | |
| return String(e.name || e.packageName || e['bom-ref'] || e.bomRef || e.SPDXID || e.spdxId || ''); | |
| } | |
| // ⇒ ★JSON 이 아니면 null 을 돌려준다 (그러면 ★줄 규칙만 돈다) | |
| function analyzeJson(raw) { | |
| var doc; | |
| try { doc = JSON.parse(raw); } catch (e) { return null; } | |
| if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null; | |
| var hits = [], i, r, j; | |
| for (i = 0; i < JRULES.length; i++) { | |
| r = JRULES[i]; j = r.json || {}; | |
| if (!jWhen(doc, j.when)) continue; | |
| if (j.kind === 'doc') { | |
| if (!jAny(doc, j.paths)) hits.push({ line: 1, msg: r.message, sev: r.sev || 'warn' }); | |
| } else if (j.kind === 'ver') { | |
| var got = jNum(jVal(doc, j.path)), min = jNum(j.min); | |
| if (!got) hits.push({ line: 1, msg: r.message + ' — found: none', sev: r.sev || 'error' }); | |
| else if (jCmp(got, min) < 0) hits.push({ line: jLine(raw, j.path), | |
| msg: r.message + ' — found: ' + got.join('.'), sev: r.sev || 'error' }); | |
| } else if (j.kind === 'each' || j.kind === 'each_bad') { | |
| var arr = jList(doc, j), miss = [], k, e, v, sv; | |
| for (k = 0; k < arr.length; k++) { | |
| e = arr[k]; | |
| if (j.kind === 'each') { if (!jAny(e, j.paths)) miss.push(e); } | |
| else { | |
| v = jVal(e, j.path); | |
| sv = jHas(v) ? String(v).trim().toLowerCase() : ''; | |
| if ((j.bad || []).indexOf(sv) >= 0) miss.push(e); | |
| } | |
| } | |
| if (miss.length) { | |
| var ex = miss.slice(0, 4).map(jName).filter(Boolean); | |
| hits.push({ line: jLine(raw, jName(miss[0])), | |
| msg: r.message + ' — ' + miss.length + ' of ' + arr.length | |
| + (ex.length ? ' (e.g. ' + ex.join(', ') + ')' : ''), | |
| sev: r.sev || 'error' }); | |
| } | |
| } | |
| } | |
| return hits; | |
| } | |
| function scan(t){var L=String(t).split(/\r?\n/),h=[],i,r,re; | |
| // ★s138 — ★확장과 ★같은 분석기를 ★먼저 돌린다 (⛔답이 다르면 거짓말이다) | |
| h=(JRULES.length?(analyzeJson(t)||[]):[]); | |
| for(i=0;i<L.length;i++){for(var k=0;k<RULES.length;k++){r=RULES[k]; | |
| if(!r||!r.pattern)continue; | |
| try{re=new RegExp(r.pattern,r.flags||'')}catch(e){continue} | |
| if(re.test(L[i]))h.push({line:i+1,msg:r.message})}} | |
| return h} | |
| document.getElementById('go').onclick=function(){ | |
| var t=document.getElementById('src').value,o=document.getElementById('out'); | |
| if(!t.trim()){o.innerHTML='';return} | |
| var h=scan(t); | |
| if(!h.length){o.innerHTML='<div class="ok">No consent-mode problems found in this file.</div>';return} | |
| var s='<table><tr><th>Line</th><th>Finding</th></tr>'; | |
| for(var i=0;i<h.length;i++){s+='<tr><td class="n">'+h[i].line+'</td><td>'+ | |
| String(h[i].msg).replace(/&/g,'&').replace(/</g,'<')+'</td></tr>'} | |
| o.innerHTML=s+'</table>'}; | |
| </script> | |
| </html> | |
| <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-mode-v2-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'consent-mode-v2-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-mode-v2-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 Mode v2 Lint for GA4 & Google Ads 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-mode-v2-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);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},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 full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Runs the same 20 rules over every file in the repository in one pass, exports a CSV/JSON/HTML report for a client or DPO, and returns CI-readable JSON so a broken consent block fails the build.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A freelancer's consent-mode setup or audit is commonly quoted at $400-$1,200; a full agency implementation at $1,200-$2,500+.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_ocwoiYU2se1AQ7U833HEc0yqBbp1nX4M5ylxf05WMYh" 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> | |
| <!--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 {"&":"&","<":"<",">":">",'"':"""}[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> | |
| <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><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-09">Updated 2026-09-09</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-09","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku17648_result_card.jpg" alt="Consent Mode v2 Lint for GA4 & Google Ads" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Eighteen findings in a twenty-two-line file. That is what this checker returns for a developer shipping GA4 and Google Ads tags to visitors in the EEA or the UK, run against a test file built from consent-mode mistakes that really ship.</p> | |
| <p>None of those eighteen lines throws an error. That is the whole problem. Write <code>adStorage</code> instead of <code>ad_storage</code>, or <code>true</code> instead of the string <code>'granted'</code>, or <code>region: ['EU']</code> when the region parameter accepts ISO 3166-2 codes only, and the browser accepts it, the page loads, the tag fires — and Google sets no consent state at all. There is no red line in the console and no warning in the network tab. The only symptom is a number going down somewhere else, weeks later.</p> | |
| <p>That symptom got more expensive on 15 June 2026. Until then, two things jointly decided whether advertising data reached Google Ads: the Google Signals toggle in the GA4 property, and the <code>ad_storage</code> consent signal. Since that date they are separate. Google Signals now only governs behavioural reporting inside GA4, and <code>ad_storage</code> alone decides what reaches Google Ads. A loose consent implementation that was quietly carried by the old fallback now simply loses advertising data, on the day of the switch, with no notification.</p> | |
| <p>The obvious tools do not catch this. Google Tag Assistant and the browser console inspect a page that is already deployed and already loading tags. They cannot read a consent block sitting in a feature branch, a component that only renders on one route, or a landing page nobody has opened in months. By the time a deployed-page checker can see the mistake, the mistake has already been live.</p> | |
| <p>So the check belongs in the editor. Consent Mode v2 Lint carries 20 rules: 18 that read hand-written <code>gtag()</code>, HTML and JS — misspelled and camelCase consent keys, boolean, numeric and unquoted values where Google requires <code>'granted'</code> or <code>'denied'</code>, invalid region shorthands, a <code>wait_for_update</code> under 500 ms or passed as a string, <code>url_passthrough</code> and <code>ads_data_redaction</code> switched off, <code>dataLayer.push(['consent', ...])</code>, <code>'defaults'</code> and <code>'updates'</code> command typos, a consent default that grants an advertising signal, <code>allow_google_signals</code> after the June 2026 change, leftover placeholder tag IDs, and <code>anonymize_ip</code>, which does nothing in GA4. Two more rules read a GTM container export: tags carrying no consent settings at all, and Google Ads conversion tags marked *No additional consent required*.</p> | |
| <p>Every finding names the line and the correct value. Checking the file you have open is free and finished — all 20 rules, nothing withheld, no watermark and no trial clock — and it will also list every rule it carries, so you can see what was not tested. What the licence adds is scope: every file in the repository in one pass, an exportable CSV, JSON or HTML report to hand a client or a DPO, and machine-readable output so CI fails the build instead of production. A freelancer's consent-mode audit is commonly quoted at $400–$1,200.</p></div><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 Mode v2 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 gtag or GTM code and reports consent-signal mistakes that browsers accept silently. It catches misspelled and camelCase consent keys, boolean or unquoted values where Google requires the strings 'granted' and 'denied', invalid region codes, a too-short wait_for_update, and consent defaults that grant advertising signals. Twenty rules, each reporting a line number and the correct value.</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 this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Web developers, freelancers and agencies who ship GA4 or Google Ads tags to visitors in the EEA or the UK. It is aimed at whoever owns the tagging code in the repository rather than the marketer who owns the Google Ads account, because these mistakes live in source files and branches, not in the ad platform interface.</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 can't I just use Google Tag Assistant or the browser console?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Tag Assistant and the console inspect a page that is already deployed and already loading tags. They cannot read a consent block sitting in a feature branch, a component that renders on one route, or a landing page nobody has opened in months. This runs against source files in your editor, before the code ships and before data is lost.</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 does the licence add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free checks the file you have open, or just the lines you select, against all twenty rules, and lists every rule so you can see what was not tested. Nothing is watermarked or time-limited. The licence widens the scope: every file in the repository at once, an exportable CSV, JSON or HTML report, and machine-readable output for CI.</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 this cost compared with hiring someone?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The licence is $29 once,. A freelancer's consent-mode setup or audit is commonly quoted at $400-$1,200, and a full agency implementation at $1,200-$2,500 or more, depending on how many tags, platforms and consent categories the site uses. The free tier stays free and is not time-limited.</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 Mode v2 Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your gtag or GTM code and reports consent-signal mistakes that browsers accept silently. It catches misspelled and camelCase consent keys, boolean or unquoted values where Google requires the strings 'granted' and 'denied', invalid region codes, a too-short wait_for_update, and consent defaults that grant advertising signals. Twenty rules, each reporting a line number and the correct value."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Web developers, freelancers and agencies who ship GA4 or Google Ads tags to visitors in the EEA or the UK. It is aimed at whoever owns the tagging code in the repository rather than the marketer who owns the Google Ads account, because these mistakes live in source files and branches, not in the ad platform interface."}}, {"@type": "Question", "name": "Why can't I just use Google Tag Assistant or the browser console?", "acceptedAnswer": {"@type": "Answer", "text": "Tag Assistant and the console inspect a page that is already deployed and already loading tags. They cannot read a consent block sitting in a feature branch, a component that renders on one route, or a landing page nobody has opened in months. This runs against source files in your editor, before the code ships and before data is lost."}}, {"@type": "Question", "name": "What is free and what does the licence add?", "acceptedAnswer": {"@type": "Answer", "text": "Free checks the file you have open, or just the lines you select, against all twenty rules, and lists every rule so you can see what was not tested. Nothing is watermarked or time-limited. The licence widens the scope: every file in the repository at once, an exportable CSV, JSON or HTML report, and machine-readable output for CI."}}, {"@type": "Question", "name": "What does this cost compared with hiring someone?", "acceptedAnswer": {"@type": "Answer", "text": "The licence is $29 once,. A freelancer's consent-mode setup or audit is commonly quoted at $400-$1,200, and a full agency implementation at $1,200-$2,500 or more, depending on how many tags, platforms and consent categories the site uses. The free tier stays free and is not time-limited."}}]}</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:17648,slug:"consent-mode-v2-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><!--yj-embed-s178--><details id="embed" data-yj="yj-embed" style="max-width:46rem;margin:1.6rem auto;padding:.85rem 1.1rem;border:1px solid #8884;border-radius:10px"><summary style="cursor:pointer;font-weight:600">Embed this tool on your site (free)</summary><p style="margin:.6rem 0;opacity:.85">Paste this code into your page. It runs in your visitor’s browser and stays up to date. Nothing your visitors type is stored.</p><textarea readonly rows="6" style="width:100%;font:12px/1.4 ui-monospace,Menlo,Consolas,monospace;border:1px solid #8886;border-radius:7px;padding:.5rem"><iframe src="https://getreadystack.com/embed/consent-mode-v2-lint/" title="Consent Mode v2 Lint for GA4 &amp; Google Ads" loading="lazy" data-rs-embed="en-consent-mode-v2-lint" style="width:100%;max-width:760px;height:640px;border:1px solid #e3e3e3;border-radius:10px"></iframe> | |
| <p style="font-size:13px;margin:6px 0 0"><a href="https://getreadystack.com/tools/consent-mode-v2-lint">Consent Mode v2 Lint for GA4 &amp; Google Ads</a> by <a href="https://getreadystack.com/">ReadyStack</a></p> | |
| <script>window.addEventListener("message",function(e){if(e.origin!=="https://getreadystack.com"||!e.data||e.data.type!=="readystack-embed")return;var f=document.querySelectorAll("iframe[data-rs-embed]");for(var i=0;i<f.length;i++){if(f[i].contentWindow===e.source)f[i].style.height=Math.min(e.data.h+6,8000)+"px";}});</script></textarea><p style="margin:.5rem 0 0"><button type="button" data-yj="yj-embed-copy" style="font:inherit;padding:8px 14px;border:1px solid #8884;border-radius:9px;background:transparent;cursor:pointer">Copy code</button> <a href="/embed/">All embeddable tools</a></p><p style="margin:.7rem 0 0;font-size:.92em;opacity:.9">For businesses: your name and logo instead of ours, and each visitor’s result + e-mail in your inbox (only when they tick consent) — Pro from $19/month. <a href="/embed/pro/?ref=embed_box">See Pro →</a></p></details><script data-yj="yj-embed">(function(){var b=document.querySelector("[data-yj=yj-embed-copy]");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var a=b.closest("details").querySelector("textarea");var v=a.value;function d(){b.textContent="Copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(v).then(d,function(){a.select();});}else{a.select();}});})();</script><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/n8n-social-media-workflow-agency-content-approval"><span class="yj-rel-t">n8n Social Media Workflow for Agency Content Approval — 6 Client Sign-Off Auto</span><span class="yj-rel-p">$59</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/base-image-eol-lint"><span class="yj-rel-t">Base Image EOL Lint - Dockerfile and CI end-of-life check</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/biometric-consent-audit"><span class="yj-rel-t">Biometric Consent Audit (BIPA / CUBI / CO)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/consent-proof-record-lint"><span class="yj-rel-t">Consent Proof Record Lint (GDPR Art.7)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/play-release-blocker-lint"><span class="yj-rel-t">Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB</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> | |
| <script data-yj="yj-prefill-s181 v1">(function(){try{var q=new URLSearchParams(location.search);if(!q.toString())return;var P={"R": {"employee": ["employee|w-?2\\b|arbeitnehmer|angestellt|会社員|従業員|給与所得|trabajador|asalariad|empregad|clt\\b|salari", "Employee"], "self": ["self-?employ|freelanc|sole (trader|prop)|selbst(ä|ae)ndig|freiberuf|個人事業|フリーランス|aut(ó|o)nom|\\bmei\\b", "Freelancer / self-employed"], "landlord": ["landlord|rental|vermiet|大家|不動産|arrendador|alquiler|locador|aluguel", "Landlord"], "investor": ["invest|trader|anleger|投資|inversor|investidor", "Investor / trader"], "employer": ["employer|arbeitgeber|雇用主|事業主|empleador|empregador", "Employer"], "seller": ["seller|online shop|h(ä|ae)ndler|販売|vendedor|e-?commerce", "Online seller"]}, "C": {"US": "^(us|usa)$|united states|estados unidos|vereinigte staaten|アメリカ|米国", "UK": "^(uk|gb)$|united kingdom|great britain|reino unido|vereinigtes k|イギリス|英国", "DE": "^de$|germany|deutschland|alemania|alemanha|ドイツ", "JP": "^jp$|japan|jap(ó|ã|o)n?|日本", "ES": "^es$|spain|españa|espana|espanha|spanien|スペイン", "BR": "^br$|brazil|brasil|ブラジル"}, "B": "your 2027 dates", "L": "en"};function ours(el){return !!(el&&el.closest&&el.closest("[data-yj],[data-yj-cta],[data-yj-cal],.yj-email-gate,.yj-find,.yj-related,#yj-ask,#yjgo,#yjr,header,footer,nav"));}function fields(){var r=document.querySelector("main")||document.body;return [].filter.call(r.querySelectorAll("input,select,textarea"),function(el){var t=(el.type||"").toLowerCase();return !ours(el)&&!el.disabled&&["email","hidden","search","password","file","submit","button","reset","image"].indexOf(t)<0;});}function key(el){return el.getAttribute("data-k")||el.id||el.name||"";}function fire(el){["input","change"].forEach(function(n){try{el.dispatchEvent(new Event(n,{bubbles:true}))}catch(e){}});}function setv(el,v){v=String(v).slice(0,80);var t=(el.type||"").toLowerCase(),tg=el.tagName;if(tg=="SELECT"){var o=[].filter.call(el.options,function(o){return o.value==v||o.text.trim()==v})[0];if(!o)return false;el.value=o.value;}else if(t=="checkbox"||t=="radio"){if(!/^(1|0|true|false|on|off|yes|no)$/i.test(v))return false;el.checked=/^(1|true|on|yes)$/i.test(v);}else if(t=="number"||t=="range"){var n=parseFloat(v.replace(/\s/g,"").replace(",","."));if(!isFinite(n))return false;el.value=String(n);}else if(t=="date"){if(!/^\d{4}-\d{2}-\d{2}$/.test(v))return false;el.value=v;}else{if(/[<>]/.test(v))return false;el.value=v;}fire(el);return true;}function pick(el,rx){if(el.tagName!="SELECT")return false;var re=new RegExp(rx,"i");var o=[].filter.call(el.options,function(o){return re.test(o.value)||re.test(o.text)})[0];if(!o||el.value==o.value)return false;el.value=o.value;fire(el);return true;}function run(){var F=fields(),done={},n=0;q.forEach(function(v,k){if(n>=20||/^(ref|role|cc|utm_.*|fbclid|gclid)$/.test(k)||!/^[A-Za-z0-9_-]{1,40}$/.test(k))return;F.forEach(function(el){if(key(el)===k&&!done[k]&&setv(el,v)){done[k]=1;n++;}});});var cc=(q.get("cc")||"").toUpperCase().replace(/[^A-Z]/g,"");if(P.C[cc])F.forEach(function(el){var k=key(el);if(!done[k]&&/country|land|pa(i|í)s|pays|国/i.test(k+" "+(el.id||""))&&pick(el,P.C[cc])){done[k]=1;n++;}});var ro=(q.get("role")||"").replace(/[^a-z]/g,""),R=P.R[ro];if(R)F.forEach(function(el){var k=key(el);if(!done[k]&&el.tagName=="SELECT"&&[].some.call(el.options,function(o){return [].some.call(Object.keys(P.R),function(x){return new RegExp(P.R[x][0],"i").test(o.text)})})&&pick(el,R[0])){done[k]=1;n++;}});if(q.get("ref")=="calendar"){var h=document.querySelector("main h1")||document.querySelector("h1");var d=document.createElement("div");d.setAttribute("data-yj","yj-prefill-back");d.style.cssText="margin:.4rem 0 .8rem;font:15px/1.5 system-ui,sans-serif;opacity:.9";var a=document.createElement("a");a.href="/"+P.L+"/calendar/?ref=tool"+(R?"&role="+ro:"")+(P.C[cc]?"&cc="+cc:"");a.textContent=(R?R[1]+" \u00b7 ":"")+P.B+" \u2192";d.appendChild(a);if(h&&h.parentNode)h.parentNode.insertBefore(d,h.nextSibling);}document.documentElement.setAttribute("data-yj-prefilled",String(n));}if(document.readyState=="loading")document.addEventListener("DOMContentLoaded",run);else run();}catch(e){}})();</script> | |