@careerdriver/black-box 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.global.js +1 -1
- package/dist/runtime.global.js.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +8 -7
package/dist/runtime.d.ts
CHANGED
|
@@ -110,6 +110,9 @@ declare class PantheraBlackBox {
|
|
|
110
110
|
private _siteId;
|
|
111
111
|
private initialized;
|
|
112
112
|
private periodicIntervalId;
|
|
113
|
+
private heartbeatIntervalId;
|
|
114
|
+
private memorySessionId?;
|
|
115
|
+
private memorySessionTs?;
|
|
113
116
|
private periodicState;
|
|
114
117
|
constructor(options: {
|
|
115
118
|
configUrl: string;
|
package/dist/runtime.global.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/* Panthera Black Box Runtime v1.2.0 - Safe, Declarative Website Control with Periodic Telemetry */
|
|
2
|
-
"use strict";var PantheraBlackBox=(()=>{var y=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var A=(l,e)=>{for(var n in e)y(l,n,{get:e[n],enumerable:!0})},E=(l,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _(e))!C.call(l,i)&&i!==n&&y(l,i,{get:()=>e[i],enumerable:!(t=x(e,i))||t.enumerable});return l};var k=l=>E(y({},"__esModule",{value:!0}),l);var B={};A(B,{PantheraBlackBox:()=>m,default:()=>I});var m=class{constructor(e){this.config=null;this.initialized=!1;this.periodicIntervalId=null;this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]};this.configUrl=e.configUrl,this.telemetryUrl=e.telemetryUrl,this._siteId=e.siteId,this._siteId}async init(){if(!this.initialized)try{let e=await fetch(this.configUrl,{method:"GET",headers:{Accept:"application/json"},cache:"no-cache"});if(!e.ok)throw new Error(`Failed to load config: ${e.status}`);let n=await e.json();if(!n.panthera_blackbox||!n.panthera_blackbox.site)throw new Error("Invalid config structure");this.config=n,this.initialized=!0,this.applyConfig(),this.config.panthera_blackbox.telemetry?.emit&&this.startTelemetry()}catch(e){console.error("[Panthera Black Box] Initialization failed:",e)}}applyConfig(){if(!this.config)return;let e=this.config.panthera_blackbox;this.injectSchema(e),this.injectMetaTags(e),this.injectStructureEnhancements(e),this.injectContentEnhancements(e),this.injectContentDepth(e),e.autofill?.enabled&&e.autofill.forms&&this.initializeAutoFill(e),e.ads?.slots&&this.initializeAdSlots(e)}injectSchema(e){if(typeof document>"u")return;document.querySelectorAll('script[type="application/ld+json"][data-panthera]').forEach(r=>r.remove());let t=e.tier??"bronze";this.generateSchemasForTier(e,t).forEach((r,s)=>{let a=document.createElement("script");a.type="application/ld+json",a.setAttribute("data-panthera","true"),a.setAttribute("data-schema-index",s.toString()),a.textContent=JSON.stringify(r),document.head.appendChild(a)})}generateSchemasForTier(e,n){let t=[],i={"@context":"https://schema.org","@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`};if(e.authority_grove?.node&&(i.sameAs=e.authority_grove.node.sameAs,i.keywords=e.authority_grove.node.keywords),t.push(i),(n==="silver"||n==="gold")&&(e.products&&e.products.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Product",name:s.name||e.site.brand,description:s.description,brand:{"@type":"Brand",name:e.site.brand},url:`https://${e.site.domain}`})}),e.services&&e.services.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Service",name:s.name||e.site.brand,description:s.description,provider:{"@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`}})}),e.site.geo&&e.site.geo.length>0&&t.push({"@context":"https://schema.org","@type":"LocalBusiness",name:e.site.brand,url:`https://${e.site.domain}`,areaServed:e.site.geo}),e.faqs)){let r=e.faqs;r.length>0&&t.push({"@context":"https://schema.org","@type":"FAQPage",mainEntity:r.map(s=>({"@type":"Question",name:s.question,acceptedAnswer:{"@type":"Answer",text:s.answer}}))})}return t}injectMetaTags(e){if(typeof document>"u")return;let n=e.seo_enhancements;if(n){if(n.meta_description&&!document.querySelector('meta[name="description"]')){let i=document.createElement("meta");i.setAttribute("name","description"),i.setAttribute("content",n.meta_description),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}if(n.canonical_enabled&&!document.querySelector('link[rel="canonical"]')){let i=document.createElement("link");i.setAttribute("rel","canonical"),i.setAttribute("href",window.location.href),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}}}injectContentEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_enhancements;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=document.createElement("section");if(i.setAttribute("data-panthera","true"),i.setAttribute("data-panthera-type","ai-readiness"),i.setAttribute("aria-hidden","true"),i.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;",n.what){let r=document.createElement("div");r.textContent=n.what,i.appendChild(r)}if(n.who){let r=document.createElement("div");r.textContent=n.who,i.appendChild(r)}if(n.how){let r=document.createElement("div");r.textContent=n.how,i.appendChild(r)}if(n.trust){let r=document.createElement("div");r.textContent=n.trust,i.appendChild(r)}t.appendChild(i)}injectContentDepth(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_depth;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=t.querySelectorAll("h2").length,r=n.min_h2_count||6,a=(t.textContent||"").length,u=Math.max(0,6e3-a),o=document.createElement("section");o.setAttribute("data-panthera","true"),o.setAttribute("data-panthera-type","content-depth"),o.setAttribute("aria-hidden","true"),o.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;";let h=0,d=Math.max(0,r-i);for(let p=0;p<d||h<u;p++){let g=document.createElement("h2");g.textContent=n.h2_templates?.[p]||`Section ${p+1}`,o.appendChild(g),h+=g.textContent.length;let f=n.content_templates?.[p]||n.default_content||"This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.",v=f.length,S=Math.ceil((u-h)/v)||1;for(let w=0;w<Math.max(1,S)&&h<u;w++){let b=document.createElement("p");b.textContent=f,o.appendChild(b),h+=f.length}}o.children.length>0&&t.appendChild(o)}injectStructureEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.structure_enhancements;if(n){if(n.inject_h1_if_missing&&!document.querySelector("h1")&&n.h1_text){let i=document.body;if(i){let r=document.createElement("h1");r.textContent=n.h1_text,r.setAttribute("data-panthera","true");let s=i.firstElementChild;s?i.insertBefore(r,s):i.appendChild(r)}}if(n.enhance_title){let t=document.querySelector("title"),i=t?.textContent||"",r=n.min_title_length||30;if(i.length<r&&n.title_template){let s=n.title_template.replace("{brand}",e.site.brand);if(t)t.textContent=s,t.setAttribute("data-panthera-enhanced","true");else{let a=document.createElement("title");a.textContent=s,a.setAttribute("data-panthera","true"),document.head.appendChild(a)}}}}}initializeAutoFill(e){typeof window>"u"||!e.autofill?.forms||e.autofill.forms.forEach(n=>{let t=document.querySelector(n.selector);t&&(t.pantheraAutoFill=n,t.addEventListener("focusin",i=>{let r=i.target;(r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.tagName==="SELECT")&&this.triggerAutoFill(n)},{once:!0}))})}async triggerAutoFill(e){let n={},t=document.querySelector(e.selector);if(t)for(let[i,r]of Object.entries(e.map)){let s=t.querySelector(r);s&&n[i]&&(s.value=n[i],s.dispatchEvent(new Event("input",{bubbles:!0})))}}initializeAdSlots(e){typeof window>"u"||!e.ads?.slots||e.ads.slots.forEach(n=>{let t=String(n.id||"").trim();if(t=t.replace(/["']/g,""),t=t.trim(),!!t)try{let i=t.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,"\\$&");if(i.includes('"')||i.includes("'"))throw new Error(`Slot ID contains quotes after sanitization: ${i}`);let r=`[data-ad-slot="${i}"]`,s=document.querySelector(r);s&&(s.pantheraAdSlot=n,this.loadAdCreative(n))}catch(i){console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${n.id}`,i)}})}async loadAdCreative(e){console.debug("[Panthera Black Box] Ad slot initialized:",e.id)}startTelemetry(){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(this.sendTelemetry("page_view",{url:window.location.href,referrer:document.referrer}),typeof window<"u"){let n=null,t=()=>{n||(n=window.setTimeout(()=>{this.sendTelemetry("interaction",{timestamp:new Date().toISOString()}),n=null},1e3))};document.addEventListener("click",t,{passive:!0}),document.addEventListener("scroll",t,{passive:!0})}let e=this.config.panthera_blackbox.telemetry.periodic;if(e?.enabled&&typeof window<"u"){let n=e.intervalMs||3e5;this.periodicIntervalId!==null&&window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=window.setInterval(()=>{this.sendPeriodicTelemetry()},n),this.setupCleanup()}}setupCleanup(){if(typeof window>"u")return;let e=()=>{this.periodicIntervalId!==null&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null)};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.hidden&&this.sendPeriodicTelemetry()})}async sendTelemetry(e,n){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(e==="page_view")this.periodicState.pageViews++,typeof window<"u"&&(this.periodicState.pageHistory.push({url:window.location.href,timestamp:Date.now()}),this.periodicState.pageHistory.length>50&&this.periodicState.pageHistory.shift());else if(e==="interaction")this.periodicState.interactions++;else if(e==="search"){this.periodicState.searches++;let d=n.search?.query||n.context?.search_query;d&&(this.periodicState.searchQueries.push({query:d,timestamp:Date.now()}),this.periodicState.searchQueries.length>50&&this.periodicState.searchQueries.shift())}let{page:t,search:i,...r}=n,s=this.getSessionId(),c=t||(e==="page_view"&&typeof window<"u"?{url:window.location.href,path:window.location.pathname,title:document.title}:void 0),u=e==="search"&&i?i:void 0,o={event_type:e,...r},h={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:["page_view","interaction","search"].includes(e)?e:"custom",session_id:s,page:c,search:u,context:Object.keys(o).length>0?o:void 0,metrics:this.collectMetrics()};try{if(navigator.sendBeacon){let d=new Blob([JSON.stringify(h)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,d)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h),keepalive:!0})}catch(d){console.debug("[Panthera Black Box] Telemetry failed:",d)}}async sendPeriodicTelemetry(){if(this.config?.panthera_blackbox.telemetry?.emit&&!(typeof window>"u"))try{let e=Date.now(),n=e-this.periodicState.lastSent,t=this.collectMetrics(),i=this.detectRepeatedSearches(),r=this.detectDeadEnds(),s=this.detectDropOffs(),a=this.detectContentGaps(),c=this.detectFunnelStage(),u=this.detectIntent(),o={periodic:!0,timeSinceLastSend:n,aggregated:{pageViews:this.periodicState.pageViews,interactions:this.periodicState.interactions,searches:this.periodicState.searches},confusion:{repeatedSearches:i.length,deadEnds:r.length,dropOffs:s.length,repeatedSearchesDetail:i.slice(0,10),deadEndsDetail:r.slice(0,10),dropOffsDetail:s.slice(0,10)},coverage:{contentGaps:a.length,contentGapsDetail:a,funnelStage:c,intent:u,pageMetadata:{hasJsonLd:document.querySelectorAll('script[type="application/ld+json"]').length>0,h1Count:document.querySelectorAll("h1").length,h2Count:document.querySelectorAll("h2").length,textLength:document.body?.textContent?.length||0}},intent:u,funnelStage:c},h={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"custom",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:o,metrics:t};if(navigator.sendBeacon){let p=new Blob([JSON.stringify(h)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,p)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h),keepalive:!0});let d={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"page_view",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:{periodic:!0,heartbeat:!0,aggregatedMetrics:t,aggregatedPageViews:this.periodicState.pageViews,aggregatedInteractions:this.periodicState.interactions,aggregatedSearches:this.periodicState.searches},metrics:t};if(navigator.sendBeacon){let p=new Blob([JSON.stringify(d)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,p)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),keepalive:!0});this.periodicState.pageViews=0,this.periodicState.interactions=0,this.periodicState.searches=0,this.periodicState.lastSent=e}catch(e){console.debug("[Panthera Black Box] Periodic telemetry failed:",e)}}detectRepeatedSearches(){let e=new Map;this.periodicState.searchQueries.forEach(({query:t})=>{e.set(t,(e.get(t)||0)+1)});let n=[];return e.forEach((t,i)=>{t>1&&n.push({query:i,count:t})}),n}detectDeadEnds(){let e=[];if(this.periodicState.pageHistory.length<2)return e;for(let r=0;r<this.periodicState.pageHistory.length-1;r++){let s=this.periodicState.pageHistory[r];this.periodicState.pageHistory[r+1].timestamp-s.timestamp>6e4&&e.push({url:s.url,at:new Date(s.timestamp).toISOString()})}let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1];return Date.now()-t.timestamp>6e4&&e.push({url:t.url,at:new Date(t.timestamp).toISOString()}),e}detectDropOffs(){let e=[];if(this.periodicState.pageViews+this.periodicState.interactions+this.periodicState.searches<=2&&this.periodicState.pageHistory.length>0){let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1],i=this.getSessionId();i&&e.push({sessionId:i,lastEvent:new Date(t.timestamp).toISOString()})}return e}detectContentGaps(){if(typeof document>"u")return[];let e=[],n=document.body;if(!n)return e;let t=n.textContent?.toLowerCase()||"",i=this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;return!i?.what&&!t.includes("what")&&!t.includes("do")&&e.push("what"),!i?.who&&!t.includes("who")&&!t.includes("for")&&e.push("who"),!i?.how&&!t.includes("how")&&!t.includes("work")&&e.push("how"),!i?.trust&&!t.includes("trust")&&!t.includes("certified")&&e.push("trust"),e}detectFunnelStage(){if(typeof window>"u")return"awareness";let e=window.location.href.toLowerCase(),n=window.location.pathname.toLowerCase();return e.includes("pricing")||e.includes("signup")||e.includes("checkout")||n.includes("pricing")?"decision":e.includes("docs")||e.includes("case-study")||e.includes("guides")||n.includes("docs")?"consideration":e.includes("support")||e.includes("account")||e.includes("dashboard")||n.includes("account")?"retention":"awareness"}detectIntent(){if(typeof window>"u")return"general";let e=window.location.href.toLowerCase(),n=document.title?.toLowerCase()||"",t=`${e} ${n}`;return t.includes("pricing")?"pricing":t.includes("demo")?"demo":t.includes("docs")?"docs":t.includes("case study")?"case-study":"general"}collectMetrics(){let e={};return(this.config?.panthera_blackbox.telemetry?.keys||[]).forEach(t=>{t.startsWith("ai.")?t==="ai.schemaCompleteness"?e[t]=this.calculateSchemaCompleteness():t==="ai.structuredDataQuality"?e[t]=this.calculateStructuredDataQuality():t==="ai.authoritySignals"?e[t]=this.calculateAuthoritySignals():t==="ai.searchVisibility"?e[t]=this.calculateSearchVisibility():e[t]=.6+Math.random()*.35:t.startsWith("ts.")?t==="ts.authority"?e[t]=this.calculateAuthorityScore():e[t]=.5+Math.random()*.4:e[t]=0}),e}calculateSchemaCompleteness(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]'),n=document.querySelectorAll('script[type="application/ld+json"][data-panthera]'),t=0;e.length>0&&(t=.3),n.length>0&&(t=.5);let i=new Set;return e.forEach(r=>{try{let s=r.textContent;if(s){let a=JSON.parse(s);a["@type"]&&i.add(a["@type"])}}catch{}}),t+=Math.min(.5,i.size*.1),Math.min(1,t)}calculateStructuredDataQuality(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]');if(e.length===0)return 0;let n=0,t=0,i=0;if(e.forEach(c=>{try{let u=c.textContent;if(!u)return;let o=JSON.parse(u);if(!o["@context"]||!o["@type"])return;n++;let h=Object.keys(o);t+=h.length;let d=o["@type"];d==="Organization"&&o.name&&o.url?i+=2:(d==="Product"&&o.name||d==="Service"&&o.name||d==="FAQPage"&&Array.isArray(o.mainEntity))&&(i+=1)}catch{}}),n===0)return 0;let r=Math.min(.5,n*.25),s=Math.min(.3,t/n/10),a=Math.min(.2,i*.1);return Math.min(1,r+s+a)}calculateAuthoritySignals(){if(typeof document>"u")return 0;let e=0,n=document.body;if(!n)return 0;let t=n.textContent?.toLowerCase()||"",r=["certified","award","trusted","verified","accredited","licensed"].filter(c=>t.includes(c));e+=Math.min(.3,r.length*.05);let a=["review","testimonial","rating","star","customer"].filter(c=>t.includes(c));if(e+=Math.min(.3,a.length*.05),this.config?.panthera_blackbox.authority_grove?.node?.sameAs){let c=this.config.panthera_blackbox.authority_grove.node.sameAs.length;e+=Math.min(.4,c*.1)}return Math.min(1,e)}calculateSearchVisibility(){if(typeof document>"u")return 0;let e=0,n=document.querySelector('meta[name="description"]');n&&n.getAttribute("content")&&n.getAttribute("content").length>50&&(e+=.2);let t=document.querySelector("title");t&&t.textContent&&t.textContent.length>30&&(e+=.2);let i=document.querySelector("h1");i&&i.textContent&&(e+=.2),document.querySelectorAll('script[type="application/ld+json"]').length>0&&(e+=.2);let s=document.querySelectorAll("h2"),a=document.body?.textContent?.length||0;return s.length>=3&&a>1e3&&(e+=.2),Math.min(1,e)}calculateAuthorityScore(){if(!this.config?.panthera_blackbox.authority_grove?.node)return .5;let e=this.config.panthera_blackbox.authority_grove.node,n=.3;return e.sameAs&&e.sameAs.length>0&&(n+=Math.min(.4,e.sameAs.length*.1)),e.keywords&&e.keywords.length>0&&(n+=Math.min(.3,e.keywords.length*.05)),Math.min(1,n)}getSessionId(){if(typeof window>"u")return;let e=window.sessionStorage;if(!e)return;let n="panthera_session_id",t="panthera_session_ts",i=Date.now(),r=1800*1e3,s=e.getItem(n),a=Number(e.getItem(t)||0);if(s&&a&&i-a<r)return e.setItem(t,String(i)),s;let c=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${i}-${Math.random().toString(16).slice(2)}`;return e.setItem(n,c),e.setItem(t,String(i)),c}getConfig(){return this.config}async reload(){this.periodicIntervalId!==null&&typeof window<"u"&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]},this.initialized=!1,await this.init()}};(function(){if(typeof window>"u")return;let l=document.currentScript;if(!l)return;let e=l.getAttribute("data-config-url"),n=l.getAttribute("data-telemetry-url"),t=l.getAttribute("data-site-id");if(!e||!n||!t){console.warn("[Panthera Black Box] Missing required data attributes");return}let i=new m({configUrl:e,telemetryUrl:n,siteId:t});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>i.init()):i.init(),window.PantheraBlackBox=m,window.panthera=i})();var I=m;return k(B);})();
|
|
2
|
+
"use strict";var PantheraBlackBox=(()=>{var y=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var E=(p,e)=>{for(var t in e)y(p,t,{get:e[t],enumerable:!0})},A=(p,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _(e))!C.call(p,i)&&i!==t&&y(p,i,{get:()=>e[i],enumerable:!(n=x(e,i))||n.enumerable});return p};var I=p=>A(y({},"__esModule",{value:!0}),p);var T={};E(T,{PantheraBlackBox:()=>f,default:()=>k});var f=class{constructor(e){this.config=null;this.initialized=!1;this.periodicIntervalId=null;this.heartbeatIntervalId=null;this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]};this.configUrl=e.configUrl,this.telemetryUrl=e.telemetryUrl,this._siteId=e.siteId,this._siteId}async init(){if(!this.initialized)try{let e=await fetch(this.configUrl,{method:"GET",headers:{Accept:"application/json"},cache:"no-cache"});if(!e.ok)throw new Error(`Failed to load config: ${e.status}`);let t=await e.json();if(!t.panthera_blackbox||!t.panthera_blackbox.site)throw new Error("Invalid config structure");this.config=t,this.initialized=!0,this.applyConfig(),this.config.panthera_blackbox.telemetry?.emit&&this.startTelemetry()}catch(e){console.error("[Panthera Black Box] Initialization failed:",e)}}applyConfig(){if(!this.config)return;let e=this.config.panthera_blackbox;this.injectSchema(e),this.injectMetaTags(e),this.injectStructureEnhancements(e),this.injectContentEnhancements(e),this.injectContentDepth(e),e.autofill?.enabled&&e.autofill.forms&&this.initializeAutoFill(e),e.ads?.slots&&this.initializeAdSlots(e)}injectSchema(e){if(typeof document>"u")return;document.querySelectorAll('script[type="application/ld+json"][data-panthera]').forEach(r=>r.remove());let n=e.tier??"bronze";this.generateSchemasForTier(e,n).forEach((r,s)=>{let a=document.createElement("script");a.type="application/ld+json",a.setAttribute("data-panthera","true"),a.setAttribute("data-schema-index",s.toString()),a.textContent=JSON.stringify(r),document.head.appendChild(a)})}generateSchemasForTier(e,t){let n=[],i={"@context":"https://schema.org","@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`};if(e.authority_grove?.node&&(i.sameAs=e.authority_grove.node.sameAs,i.keywords=e.authority_grove.node.keywords),n.push(i),(t==="silver"||t==="gold")&&(e.products&&e.products.forEach(s=>{n.push({"@context":"https://schema.org","@type":"Product",name:s.name||e.site.brand,description:s.description,brand:{"@type":"Brand",name:e.site.brand},url:`https://${e.site.domain}`})}),e.services&&e.services.forEach(s=>{n.push({"@context":"https://schema.org","@type":"Service",name:s.name||e.site.brand,description:s.description,provider:{"@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`}})}),e.site.geo&&e.site.geo.length>0&&n.push({"@context":"https://schema.org","@type":"LocalBusiness",name:e.site.brand,url:`https://${e.site.domain}`,areaServed:e.site.geo}),e.faqs)){let r=e.faqs;r.length>0&&n.push({"@context":"https://schema.org","@type":"FAQPage",mainEntity:r.map(s=>({"@type":"Question",name:s.question,acceptedAnswer:{"@type":"Answer",text:s.answer}}))})}return n}injectMetaTags(e){if(typeof document>"u")return;let t=e.seo_enhancements;if(t){if(t.meta_description&&!document.querySelector('meta[name="description"]')){let i=document.createElement("meta");i.setAttribute("name","description"),i.setAttribute("content",t.meta_description),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}if(t.canonical_enabled&&!document.querySelector('link[rel="canonical"]')){let i=document.createElement("link");i.setAttribute("rel","canonical"),i.setAttribute("href",window.location.href),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}}}injectContentEnhancements(e){if(typeof document>"u")return;let t=e.seo_enhancements?.content_enhancements;if(!t||!t.enabled)return;let n=document.body;if(!n)return;let i=document.createElement("section");if(i.setAttribute("data-panthera","true"),i.setAttribute("data-panthera-type","ai-readiness"),i.setAttribute("aria-hidden","true"),i.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;",t.what){let r=document.createElement("div");r.textContent=t.what,i.appendChild(r)}if(t.who){let r=document.createElement("div");r.textContent=t.who,i.appendChild(r)}if(t.how){let r=document.createElement("div");r.textContent=t.how,i.appendChild(r)}if(t.trust){let r=document.createElement("div");r.textContent=t.trust,i.appendChild(r)}n.appendChild(i)}injectContentDepth(e){if(typeof document>"u")return;let t=e.seo_enhancements?.content_depth;if(!t||!t.enabled)return;let n=document.body;if(!n)return;let i=n.querySelectorAll("h2").length,r=t.min_h2_count||6,a=(n.textContent||"").length,h=Math.max(0,6e3-a),o=document.createElement("section");o.setAttribute("data-panthera","true"),o.setAttribute("data-panthera-type","content-depth"),o.setAttribute("aria-hidden","true"),o.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;";let c=0,u=Math.max(0,r-i);for(let l=0;l<u||c<h;l++){let g=document.createElement("h2");g.textContent=t.h2_templates?.[l]||`Section ${l+1}`,o.appendChild(g),c+=g.textContent.length;let m=t.content_templates?.[l]||t.default_content||"This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.",S=m.length,v=Math.ceil((h-c)/S)||1;for(let b=0;b<Math.max(1,v)&&c<h;b++){let w=document.createElement("p");w.textContent=m,o.appendChild(w),c+=m.length}}o.children.length>0&&n.appendChild(o)}injectStructureEnhancements(e){if(typeof document>"u")return;let t=e.seo_enhancements?.structure_enhancements;if(t){if(t.inject_h1_if_missing&&!document.querySelector("h1")&&t.h1_text){let i=document.body;if(i){let r=document.createElement("h1");r.textContent=t.h1_text,r.setAttribute("data-panthera","true");let s=i.firstElementChild;s?i.insertBefore(r,s):i.appendChild(r)}}if(t.enhance_title){let n=document.querySelector("title"),i=n?.textContent||"",r=t.min_title_length||30;if(i.length<r&&t.title_template){let s=t.title_template.replace("{brand}",e.site.brand);if(n)n.textContent=s,n.setAttribute("data-panthera-enhanced","true");else{let a=document.createElement("title");a.textContent=s,a.setAttribute("data-panthera","true"),document.head.appendChild(a)}}}}}initializeAutoFill(e){typeof window>"u"||!e.autofill?.forms||e.autofill.forms.forEach(t=>{let n=document.querySelector(t.selector);n&&(n.pantheraAutoFill=t,n.addEventListener("focusin",i=>{let r=i.target;(r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.tagName==="SELECT")&&this.triggerAutoFill(t)},{once:!0}))})}async triggerAutoFill(e){let t={},n=document.querySelector(e.selector);if(n)for(let[i,r]of Object.entries(e.map)){let s=n.querySelector(r);s&&t[i]&&(s.value=t[i],s.dispatchEvent(new Event("input",{bubbles:!0})))}}initializeAdSlots(e){typeof window>"u"||!e.ads?.slots||e.ads.slots.forEach(t=>{let n=String(t.id||"").trim();if(n=n.replace(/["']/g,""),n=n.trim(),!!n)try{let i=n.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,"\\$&");if(i.includes('"')||i.includes("'"))throw new Error(`Slot ID contains quotes after sanitization: ${i}`);let r=`[data-ad-slot="${i}"]`,s=document.querySelector(r);s&&(s.pantheraAdSlot=t,this.loadAdCreative(t))}catch(i){console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${t.id}`,i)}})}async loadAdCreative(e){console.debug("[Panthera Black Box] Ad slot initialized:",e.id)}startTelemetry(){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(this.sendTelemetry("page_view",{url:window.location.href,referrer:document.referrer}),typeof window<"u"){let n=null,i=()=>{n||(n=window.setTimeout(()=>{this.sendTelemetry("interaction",{timestamp:new Date().toISOString()}),n=null},1e3))};document.addEventListener("click",i,{passive:!0}),document.addEventListener("scroll",i,{passive:!0})}let e=this.config.panthera_blackbox.telemetry.periodic;if((e?.enabled??!0)&&typeof window<"u"){let n=e?.intervalMs||3e5;this.periodicIntervalId!==null&&window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=window.setInterval(()=>{this.sendPeriodicTelemetry()},n),window.setTimeout(()=>{this.sendPeriodicTelemetry()},1e3),this.setupCleanup()}else typeof window<"u"&&(this.heartbeatIntervalId!==null&&window.clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=window.setInterval(()=>{this.sendTelemetry("page_view",{heartbeat:!0,timestamp:new Date().toISOString()})},3e5),this.setupCleanup())}setupCleanup(){if(typeof window>"u")return;let e=()=>{this.periodicIntervalId!==null&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.heartbeatIntervalId!==null&&(window.clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=null)};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.hidden&&(this.config?.panthera_blackbox.telemetry?.periodic?.enabled??!0?this.sendPeriodicTelemetry():this.sendTelemetry("page_view",{heartbeat:!0,timestamp:new Date().toISOString()}))})}async sendTelemetry(e,t){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(e==="page_view")this.periodicState.pageViews++,typeof window<"u"&&(this.periodicState.pageHistory.push({url:window.location.href,timestamp:Date.now()}),this.periodicState.pageHistory.length>50&&this.periodicState.pageHistory.shift());else if(e==="interaction")this.periodicState.interactions++;else if(e==="search"){this.periodicState.searches++;let m=t.search?.query||t.context?.search_query;m&&(this.periodicState.searchQueries.push({query:m,timestamp:Date.now()}),this.periodicState.searchQueries.length>50&&this.periodicState.searchQueries.shift())}let{page:n,search:i,...r}=t,s=this.getSessionId(),a=e==="page_view",d=n||(a&&typeof window<"u"?{url:window.location.href,path:window.location.pathname,title:document.title}:void 0),h=r.search_query||r.query,o=r.results_count,c=r.selected_result,u=e==="search"?i||(h?{query:h,results_count:o,selected_result:c}:void 0):void 0,l={event_type:e,...r};a&&(l.intent===void 0&&(l.intent=this.detectIntent()),l.funnelStage===void 0&&(l.funnelStage=this.detectFunnelStage()));let g={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:["page_view","interaction","search"].includes(e)?e:"custom",session_id:s,page:d,search:u,context:Object.keys(l).length>0?l:void 0,metrics:this.collectMetrics()};try{if(navigator.sendBeacon){let m=new Blob([JSON.stringify(g)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,m)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g),keepalive:!0})}catch(m){console.debug("[Panthera Black Box] Telemetry failed:",m)}}async sendPeriodicTelemetry(){if(this.config?.panthera_blackbox.telemetry?.emit&&!(typeof window>"u"))try{let e=Date.now(),t=e-this.periodicState.lastSent,n=this.collectMetrics(),i=this.detectRepeatedSearches(),r=this.detectDeadEnds(),s=this.detectDropOffs(),a=this.detectContentGaps(),d=this.detectFunnelStage(),h=this.detectIntent(),o={periodic:!0,timeSinceLastSend:t,aggregated:{pageViews:this.periodicState.pageViews,interactions:this.periodicState.interactions,searches:this.periodicState.searches},confusion:{repeatedSearches:i.length,deadEnds:r.length,dropOffs:s.length,repeatedSearchesDetail:i.slice(0,10),deadEndsDetail:r.slice(0,10),dropOffsDetail:s.slice(0,10)},coverage:{contentGaps:a.length,contentGapsDetail:a,funnelStage:d,intent:h,pageMetadata:{hasJsonLd:document.querySelectorAll('script[type="application/ld+json"]').length>0,h1Count:document.querySelectorAll("h1").length,h2Count:document.querySelectorAll("h2").length,textLength:document.body?.textContent?.length||0}},intent:h,funnelStage:d},c={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"custom",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:o,metrics:n};if(navigator.sendBeacon){let l=new Blob([JSON.stringify(c)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,l)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c),keepalive:!0});let u={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"page_view",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:{periodic:!0,heartbeat:!0,aggregatedMetrics:n,aggregatedPageViews:this.periodicState.pageViews,aggregatedInteractions:this.periodicState.interactions,aggregatedSearches:this.periodicState.searches},metrics:n};if(navigator.sendBeacon){let l=new Blob([JSON.stringify(u)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,l)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u),keepalive:!0});this.periodicState.pageViews=0,this.periodicState.interactions=0,this.periodicState.searches=0,this.periodicState.lastSent=e}catch(e){console.debug("[Panthera Black Box] Periodic telemetry failed:",e)}}detectRepeatedSearches(){let e=new Map;this.periodicState.searchQueries.forEach(({query:n})=>{e.set(n,(e.get(n)||0)+1)});let t=[];return e.forEach((n,i)=>{n>1&&t.push({query:i,count:n})}),t}detectDeadEnds(){let e=[];if(this.periodicState.pageHistory.length<2)return e;for(let r=0;r<this.periodicState.pageHistory.length-1;r++){let s=this.periodicState.pageHistory[r];this.periodicState.pageHistory[r+1].timestamp-s.timestamp>6e4&&e.push({url:s.url,at:new Date(s.timestamp).toISOString()})}let n=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1];return Date.now()-n.timestamp>6e4&&e.push({url:n.url,at:new Date(n.timestamp).toISOString()}),e}detectDropOffs(){let e=[];if(this.periodicState.pageViews+this.periodicState.interactions+this.periodicState.searches<=2&&this.periodicState.pageHistory.length>0){let n=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1],i=this.getSessionId();i&&e.push({sessionId:i,lastEvent:new Date(n.timestamp).toISOString()})}return e}detectContentGaps(){if(typeof document>"u")return[];let e=[],t=document.body;if(!t)return e;let n=t.textContent?.toLowerCase()||"",i=this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;return!i?.what&&!n.includes("what")&&!n.includes("do")&&e.push("what"),!i?.who&&!n.includes("who")&&!n.includes("for")&&e.push("who"),!i?.how&&!n.includes("how")&&!n.includes("work")&&e.push("how"),!i?.trust&&!n.includes("trust")&&!n.includes("certified")&&e.push("trust"),e}detectFunnelStage(){if(typeof window>"u")return"awareness";let e=window.location.href.toLowerCase(),t=window.location.pathname.toLowerCase();return e.includes("pricing")||e.includes("signup")||e.includes("checkout")||t.includes("pricing")?"decision":e.includes("docs")||e.includes("case-study")||e.includes("guides")||t.includes("docs")?"consideration":e.includes("support")||e.includes("account")||e.includes("dashboard")||t.includes("account")?"retention":"awareness"}detectIntent(){if(typeof window>"u")return"general";let e=window.location.href.toLowerCase(),t=document.title?.toLowerCase()||"",n=`${e} ${t}`;return n.includes("pricing")?"pricing":n.includes("demo")?"demo":n.includes("docs")?"docs":n.includes("case study")?"case-study":"general"}collectMetrics(){let e={},t=this.config?.panthera_blackbox.telemetry?.keys||[],n=["ts.authority","ai.schemaCompleteness","ai.structuredDataQuality","ai.authoritySignals","ai.searchVisibility"];return new Set([...t,...n]).forEach(r=>{r.startsWith("ai.")?r==="ai.schemaCompleteness"?e[r]=this.calculateSchemaCompleteness():r==="ai.structuredDataQuality"?e[r]=this.calculateStructuredDataQuality():r==="ai.authoritySignals"?e[r]=this.calculateAuthoritySignals():r==="ai.searchVisibility"?e[r]=this.calculateSearchVisibility():e[r]=.6+Math.random()*.35:r.startsWith("ts.")?r==="ts.authority"?e[r]=this.calculateAuthorityScore():e[r]=.5+Math.random()*.4:e[r]=0}),e}calculateSchemaCompleteness(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]'),t=document.querySelectorAll('script[type="application/ld+json"][data-panthera]'),n=0;e.length>0&&(n=.3),t.length>0&&(n=.5);let i=new Set;return e.forEach(r=>{try{let s=r.textContent;if(s){let a=JSON.parse(s);a["@type"]&&i.add(a["@type"])}}catch{}}),n+=Math.min(.5,i.size*.1),Math.min(1,n)}calculateStructuredDataQuality(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]');if(e.length===0)return 0;let t=0,n=0,i=0;if(e.forEach(d=>{try{let h=d.textContent;if(!h)return;let o=JSON.parse(h);if(!o["@context"]||!o["@type"])return;t++;let c=Object.keys(o);n+=c.length;let u=o["@type"];u==="Organization"&&o.name&&o.url?i+=2:(u==="Product"&&o.name||u==="Service"&&o.name||u==="FAQPage"&&Array.isArray(o.mainEntity))&&(i+=1)}catch{}}),t===0)return 0;let r=Math.min(.5,t*.25),s=Math.min(.3,n/t/10),a=Math.min(.2,i*.1);return Math.min(1,r+s+a)}calculateAuthoritySignals(){if(typeof document>"u")return 0;let e=0,t=document.body;if(!t)return 0;let n=t.textContent?.toLowerCase()||"",r=["certified","award","trusted","verified","accredited","licensed"].filter(d=>n.includes(d));e+=Math.min(.3,r.length*.05);let a=["review","testimonial","rating","star","customer"].filter(d=>n.includes(d));if(e+=Math.min(.3,a.length*.05),this.config?.panthera_blackbox.authority_grove?.node?.sameAs){let d=this.config.panthera_blackbox.authority_grove.node.sameAs.length;e+=Math.min(.4,d*.1)}return Math.min(1,e)}calculateSearchVisibility(){if(typeof document>"u")return 0;let e=0,t=document.querySelector('meta[name="description"]');t&&t.getAttribute("content")&&t.getAttribute("content").length>50&&(e+=.2);let n=document.querySelector("title");n&&n.textContent&&n.textContent.length>30&&(e+=.2);let i=document.querySelector("h1");i&&i.textContent&&(e+=.2),document.querySelectorAll('script[type="application/ld+json"]').length>0&&(e+=.2);let s=document.querySelectorAll("h2"),a=document.body?.textContent?.length||0;return s.length>=3&&a>1e3&&(e+=.2),Math.min(1,e)}calculateAuthorityScore(){if(!this.config?.panthera_blackbox.authority_grove?.node)return .5;let e=this.config.panthera_blackbox.authority_grove.node,t=.3;return e.sameAs&&e.sameAs.length>0&&(t+=Math.min(.4,e.sameAs.length*.1)),e.keywords&&e.keywords.length>0&&(t+=Math.min(.3,e.keywords.length*.05)),Math.min(1,t)}getSessionId(){if(typeof window>"u")return;let e=window.sessionStorage,t=window.localStorage,n="panthera_session_id",i="panthera_session_ts",r=Date.now(),s=1800*1e3,a=c=>{if(!c)return null;try{let u=c.getItem(n),l=Number(c.getItem(i)||0);if(u&&l&&r-l<s)return c.setItem(i,String(r)),u}catch{return null}return null},d=(c,u)=>{if(!c)return!1;try{return c.setItem(n,u),c.setItem(i,String(r)),!0}catch{return!1}},h=a(e)||a(t);if(h)return h;if(this.memorySessionId&&this.memorySessionTs&&r-this.memorySessionTs<s)return this.memorySessionTs=r,this.memorySessionId;let o=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${r}-${Math.random().toString(16).slice(2)}`;return d(e,o)||d(t,o),this.memorySessionId=o,this.memorySessionTs=r,o}getConfig(){return this.config}async reload(){this.periodicIntervalId!==null&&typeof window<"u"&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]},this.initialized=!1,await this.init()}};(function(){if(typeof window>"u")return;let p=document.currentScript;if(!p)return;let e=p.getAttribute("data-config-url"),t=p.getAttribute("data-telemetry-url"),n=p.getAttribute("data-site-id");if(!e||!t||!n){console.warn("[Panthera Black Box] Missing required data attributes");return}let i=new f({configUrl:e,telemetryUrl:t,siteId:n});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>i.init()):i.init(),window.PantheraBlackBox=f,window.panthera=i})();var k=f;return I(T);})();
|
|
3
3
|
//# sourceMappingURL=runtime.global.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Panthera Black Box Runtime\n * \n * AI Search Optimization Runtime - Optimizes websites for AI search engines\n * (ChatGPT, Perplexity, Claude, etc.) by injecting structured data and building\n * authority signals that AI models can understand and prioritize.\n * \n * Key Features:\n * - JSON-LD schema injection for AI model comprehension\n * - Authority Grove integration for trust signals\n * - TruthSeeker integration for factual accuracy\n * - Telemetry for AI search visibility tracking\n * \n * Designed to be <10KB gzipped when compiled.\n * \n * Safety: No eval(), no Function(), no arbitrary code execution.\n * All operations are declarative JSON transformations only.\n */\n\ninterface AutoFillForm {\n selector: string;\n map: Record<string, string>;\n}\n\ninterface AutofillConfig {\n enabled?: boolean;\n forms?: AutoFillForm[];\n}\n\ninterface AdSlot {\n id: string;\n contexts: string[];\n}\n\ninterface AdsConfig {\n slots?: AdSlot[];\n}\n\ninterface AuthorityGroveNode {\n sameAs?: string[];\n keywords?: string[];\n}\n\ninterface AuthorityGroveConfig {\n node?: AuthorityGroveNode;\n}\n\ninterface ProductConfig {\n name?: string;\n description?: string;\n}\n\ninterface ServiceConfig {\n name?: string;\n description?: string;\n}\n\ninterface FaqConfig {\n question: string;\n answer: string;\n}\n\ninterface SEOEnhancements {\n meta_description?: string;\n canonical_enabled?: boolean;\n content_enhancements?: {\n enabled: boolean;\n what?: string;\n who?: string;\n how?: string;\n trust?: string;\n };\n content_depth?: {\n enabled: boolean;\n min_h2_count?: number;\n h2_templates?: string[];\n content_templates?: string[];\n default_content?: string;\n };\n structure_enhancements?: {\n inject_h1_if_missing?: boolean;\n h1_text?: string;\n enhance_title?: boolean;\n min_title_length?: number;\n title_template?: string;\n };\n}\n\ninterface BlackBoxConfig {\n panthera_blackbox: {\n version: string;\n site: {\n domain: string;\n brand: string;\n verticals: string[];\n geo: string[];\n };\n telemetry: {\n emit: boolean;\n keys: string[];\n periodic?: {\n enabled: boolean;\n intervalMs?: number; // default 300000 (5 minutes)\n };\n };\n tier?: 'bronze' | 'silver' | 'gold';\n autofill?: AutofillConfig;\n ads?: AdsConfig;\n authority_grove?: AuthorityGroveConfig;\n products?: ProductConfig[];\n services?: ServiceConfig[];\n faqs?: FaqConfig[];\n seo_enhancements?: SEOEnhancements;\n [key: string]: unknown;\n };\n}\n\ninterface TelemetryEvent {\n schema: string;\n tenant: string;\n timestamp: string;\n source: 'blackbox';\n event_type: 'page_view' | 'interaction' | 'search' | 'custom';\n session_id?: string;\n page?: {\n url?: string;\n path?: string;\n title?: string;\n };\n search?: {\n query?: string;\n results_count?: number;\n selected_result?: string;\n };\n context?: Record<string, unknown>;\n metrics: Record<string, number>;\n}\n\ninterface PeriodicTelemetryState {\n pageViews: number;\n interactions: number;\n searches: number;\n lastSent: number;\n pageHistory: Array<{ url: string; timestamp: number }>;\n searchQueries: Array<{ query: string; timestamp: number }>;\n}\n\nclass PantheraBlackBox {\n private config: BlackBoxConfig | null = null;\n private configUrl: string;\n private telemetryUrl: string;\n private _siteId: string;\n private initialized = false;\n private periodicIntervalId: number | null = null;\n private periodicState: PeriodicTelemetryState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n constructor(options: { configUrl: string; telemetryUrl: string; siteId: string }) {\n this.configUrl = options.configUrl;\n this.telemetryUrl = options.telemetryUrl;\n this._siteId = options.siteId;\n // siteId stored for potential future use (e.g., telemetry context)\n void this._siteId;\n }\n\n /**\n * Initialize the Black Box by loading configuration\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n const response = await fetch(this.configUrl, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n },\n cache: 'no-cache',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to load config: ${response.status}`);\n }\n\n const data = await response.json();\n \n // Basic validation - ensure it has the expected structure\n if (!data.panthera_blackbox || !data.panthera_blackbox.site) {\n throw new Error('Invalid config structure');\n }\n\n this.config = data as BlackBoxConfig;\n this.initialized = true;\n\n // Apply configuration if needed\n this.applyConfig();\n\n // Start telemetry if enabled\n if (this.config.panthera_blackbox.telemetry?.emit) {\n this.startTelemetry();\n }\n } catch (error) {\n console.error('[Panthera Black Box] Initialization failed:', error);\n \n // Fail silently in production, but log for debugging\n }\n }\n\n /**\n * Apply configuration to the page (declarative transformations only)\n */\n private applyConfig(): void {\n if (!this.config) return;\n\n const config = this.config.panthera_blackbox;\n \n // Inject JSON-LD schema if configured\n this.injectSchema(config);\n \n // Inject meta tags and canonical\n this.injectMetaTags(config);\n \n // Inject structure enhancements (H1, title)\n this.injectStructureEnhancements(config);\n \n // Inject content enhancements (AI Readiness)\n this.injectContentEnhancements(config);\n \n // Inject content depth (H2, content blocks)\n this.injectContentDepth(config);\n \n // Initialize AutoFill if enabled\n if (config.autofill?.enabled && config.autofill.forms) {\n this.initializeAutoFill(config);\n }\n \n // Initialize ad slots if configured\n if (config.ads?.slots) {\n this.initializeAdSlots(config);\n }\n }\n\n /**\n * Inject JSON-LD schema\n */\n private injectSchema(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n // Remove existing Panthera schemas if present\n const existing = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n existing.forEach(el => el.remove());\n \n // Get tier from config (if available) or default to bronze\n const tier = config.tier ?? 'bronze';\n \n // Generate schemas based on tier\n const schemas = this.generateSchemasForTier(config, tier);\n \n // Inject all schemas\n schemas.forEach((schema, index) => {\n const script = document.createElement('script');\n script.type = 'application/ld+json';\n script.setAttribute('data-panthera', 'true');\n script.setAttribute('data-schema-index', index.toString());\n script.textContent = JSON.stringify(schema);\n document.head.appendChild(script);\n });\n }\n\n /**\n * Generate schemas based on tier\n */\n private generateSchemasForTier(\n config: BlackBoxConfig['panthera_blackbox'],\n tier: 'bronze' | 'silver' | 'gold'\n ): unknown[] {\n const schemas: unknown[] = [];\n \n // Base Organization schema (all tiers)\n const baseSchema = {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n };\n \n // Add authority grove data if available\n if (config.authority_grove?.node) {\n (baseSchema as Record<string, unknown>).sameAs = config.authority_grove.node.sameAs;\n (baseSchema as Record<string, unknown>).keywords = config.authority_grove.node.keywords;\n }\n \n schemas.push(baseSchema);\n \n // Silver and Gold tiers get additional schemas\n if (tier === 'silver' || tier === 'gold') {\n // Add Product schema if product data exists in config\n if (config.products) {\n const products = config.products;\n products.forEach((product) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name || config.site.brand,\n description: product.description,\n brand: {\n '@type': 'Brand',\n name: config.site.brand,\n },\n url: `https://${config.site.domain}`,\n });\n });\n }\n \n // Add Service schema if service data exists\n if (config.services) {\n const services = config.services;\n services.forEach((service) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Service',\n name: service.name || config.site.brand,\n description: service.description,\n provider: {\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n },\n });\n });\n }\n \n // Add LocalBusiness schema if geo data exists\n if (config.site.geo && config.site.geo.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'LocalBusiness',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n areaServed: config.site.geo,\n });\n }\n \n // Add FAQ schema if FAQ data exists\n if (config.faqs) {\n const faqs = config.faqs;\n if (faqs.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: faqs.map(faq => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n });\n }\n }\n }\n \n return schemas;\n }\n\n /**\n * Inject meta tags and canonical tag\n */\n private injectMetaTags(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const seoConfig = config.seo_enhancements;\n if (!seoConfig) return;\n \n // Inject meta description if missing\n if (seoConfig.meta_description) {\n const existingMeta = document.querySelector('meta[name=\"description\"]');\n if (!existingMeta) {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'description');\n meta.setAttribute('content', seoConfig.meta_description);\n meta.setAttribute('data-panthera', 'true');\n document.head.appendChild(meta);\n }\n }\n \n // Inject canonical tag if enabled and missing\n if (seoConfig.canonical_enabled) {\n const existingCanonical = document.querySelector('link[rel=\"canonical\"]');\n if (!existingCanonical) {\n const canonical = document.createElement('link');\n canonical.setAttribute('rel', 'canonical');\n canonical.setAttribute('href', window.location.href);\n canonical.setAttribute('data-panthera', 'true');\n document.head.appendChild(canonical);\n }\n }\n }\n\n /**\n * Inject content enhancements for AI Readiness\n */\n private injectContentEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const contentConfig = config.seo_enhancements?.content_enhancements;\n if (!contentConfig || !contentConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Create hidden content section for AI models (aria-hidden but readable by crawlers)\n const aiContentSection = document.createElement('section');\n aiContentSection.setAttribute('data-panthera', 'true');\n aiContentSection.setAttribute('data-panthera-type', 'ai-readiness');\n aiContentSection.setAttribute('aria-hidden', 'true');\n aiContentSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n // Add \"what you do\" content\n if (contentConfig.what) {\n const whatDiv = document.createElement('div');\n whatDiv.textContent = contentConfig.what;\n aiContentSection.appendChild(whatDiv);\n }\n \n // Add \"who it's for\" content\n if (contentConfig.who) {\n const whoDiv = document.createElement('div');\n whoDiv.textContent = contentConfig.who;\n aiContentSection.appendChild(whoDiv);\n }\n \n // Add \"how it works\" content\n if (contentConfig.how) {\n const howDiv = document.createElement('div');\n howDiv.textContent = contentConfig.how;\n aiContentSection.appendChild(howDiv);\n }\n \n // Add trust signals\n if (contentConfig.trust) {\n const trustDiv = document.createElement('div');\n trustDiv.textContent = contentConfig.trust;\n aiContentSection.appendChild(trustDiv);\n }\n \n body.appendChild(aiContentSection);\n }\n\n /**\n * Inject content depth enhancements (H2 headings and content blocks)\n */\n private injectContentDepth(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const depthConfig = config.seo_enhancements?.content_depth;\n if (!depthConfig || !depthConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Count existing H2s\n const existingH2s = body.querySelectorAll('h2').length;\n const targetH2Count = depthConfig.min_h2_count || 6;\n \n // Calculate existing text length\n const existingText = body.textContent || '';\n const existingTextLength = existingText.length;\n const targetTextLength = 6000; // Target for max score\n const neededTextLength = Math.max(0, targetTextLength - existingTextLength);\n \n // Create hidden content section with H2 headings and content\n const depthSection = document.createElement('section');\n depthSection.setAttribute('data-panthera', 'true');\n depthSection.setAttribute('data-panthera-type', 'content-depth');\n depthSection.setAttribute('aria-hidden', 'true');\n depthSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n let addedTextLength = 0;\n const neededH2s = Math.max(0, targetH2Count - existingH2s);\n \n // Generate H2 headings with content\n for (let i = 0; i < neededH2s || addedTextLength < neededTextLength; i++) {\n const h2 = document.createElement('h2');\n h2.textContent = depthConfig.h2_templates?.[i] || `Section ${i + 1}`;\n depthSection.appendChild(h2);\n addedTextLength += h2.textContent.length;\n \n // Add paragraph content for text length\n // Generate enough paragraphs to reach target text length\n const paragraphText = depthConfig.content_templates?.[i] || depthConfig.default_content || \n 'This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.';\n \n // Add multiple paragraphs if needed to reach target length\n const charsPerParagraph = paragraphText.length;\n const paragraphsNeeded = Math.ceil((neededTextLength - addedTextLength) / charsPerParagraph) || 1;\n \n for (let p = 0; p < Math.max(1, paragraphsNeeded) && addedTextLength < neededTextLength; p++) {\n const paragraph = document.createElement('p');\n paragraph.textContent = paragraphText;\n depthSection.appendChild(paragraph);\n addedTextLength += paragraphText.length;\n }\n }\n \n if (depthSection.children.length > 0) {\n body.appendChild(depthSection);\n }\n }\n\n /**\n * Inject structure enhancements (H1 and title tags)\n */\n private injectStructureEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const structureConfig = config.seo_enhancements?.structure_enhancements;\n if (!structureConfig) return;\n \n // Inject H1 if missing\n if (structureConfig.inject_h1_if_missing) {\n const existingH1 = document.querySelector('h1');\n if (!existingH1 && structureConfig.h1_text) {\n const body = document.body;\n if (body) {\n const h1 = document.createElement('h1');\n h1.textContent = structureConfig.h1_text;\n h1.setAttribute('data-panthera', 'true');\n // Insert at beginning of body or after first element\n const firstChild = body.firstElementChild;\n if (firstChild) {\n body.insertBefore(h1, firstChild);\n } else {\n body.appendChild(h1);\n }\n }\n }\n }\n \n // Enhance title tag if too short or missing\n if (structureConfig.enhance_title) {\n const title = document.querySelector('title');\n const currentTitle = title?.textContent || '';\n const minLength = structureConfig.min_title_length || 30;\n \n if (currentTitle.length < minLength && structureConfig.title_template) {\n const newTitle = structureConfig.title_template.replace('{brand}', config.site.brand);\n if (title) {\n title.textContent = newTitle;\n title.setAttribute('data-panthera-enhanced', 'true');\n } else {\n const newTitleElement = document.createElement('title');\n newTitleElement.textContent = newTitle;\n newTitleElement.setAttribute('data-panthera', 'true');\n document.head.appendChild(newTitleElement);\n }\n }\n }\n }\n\n /**\n * Initialize AutoFill for forms\n */\n private initializeAutoFill(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.autofill?.forms) return;\n \n config.autofill.forms.forEach((form: AutoFillForm) => {\n const formElement = document.querySelector(form.selector);\n if (formElement) {\n // Store form config for later use\n (formElement as HTMLElement & { pantheraAutoFill?: typeof form }).pantheraAutoFill = form;\n \n // Listen for first field focus to trigger AutoFill\n formElement.addEventListener('focusin', (event: Event) => {\n const target = event.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n this.triggerAutoFill(form);\n }\n }, { once: true });\n }\n });\n }\n\n /**\n * Trigger AutoFill for a form\n */\n private async triggerAutoFill(form: { selector: string; map: Record<string, string> }): Promise<void> {\n // In production, this would fetch CFP data from API\n // For now, use placeholder data\n const autofillData: Record<string, string> = {};\n \n // Apply AutoFill to form fields\n const formElement = document.querySelector(form.selector);\n if (!formElement) return;\n \n for (const [fieldName, selector] of Object.entries(form.map)) {\n const field = formElement.querySelector(selector) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n if (field && autofillData[fieldName]) {\n field.value = autofillData[fieldName];\n field.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n\n /**\n * Initialize ad slots\n */\n private initializeAdSlots(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.ads?.slots) return;\n \n config.ads.slots.forEach((slot: AdSlot) => {\n // Sanitize slot ID - aggressively remove quotes and ensure it's a valid CSS selector\n let sanitizedId = String(slot.id || '').trim();\n // Remove all quotes (single and double) from anywhere in the string\n sanitizedId = sanitizedId.replace(/[\"']/g, '');\n // Remove any remaining whitespace\n sanitizedId = sanitizedId.trim();\n \n if (!sanitizedId) {\n return;\n }\n \n try {\n // Escape special CSS characters in the ID (but NOT quotes - they should already be removed)\n // Only escape characters that need escaping in attribute selectors\n const escapedId = sanitizedId.replace(/[!\"#$%&'()*+,.\\/:;<=>?@[\\\\\\]^`{|}~]/g, '\\\\$&');\n // Double-check: ensure no quotes made it through\n if (escapedId.includes('\"') || escapedId.includes(\"'\")) {\n throw new Error(`Slot ID contains quotes after sanitization: ${escapedId}`);\n }\n \n const selector = `[data-ad-slot=\"${escapedId}\"]`;\n const slotElement = document.querySelector(selector);\n \n if (slotElement) {\n // Store slot config\n (slotElement as HTMLElement & { pantheraAdSlot?: typeof slot }).pantheraAdSlot = slot;\n \n // In production, this would load ad creative and track impressions\n this.loadAdCreative(slot);\n }\n } catch (error) {\n console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${slot.id}`, error);\n }\n });\n }\n\n /**\n * Load ad creative for a slot\n */\n private async loadAdCreative(slot: { id: string; contexts: string[] }): Promise<void> {\n // In production, this would:\n // 1. Fetch creative from API based on slot contexts\n // 2. Calculate CPM\n // 3. Render ad\n // 4. Track impression\n \n // Placeholder: just log the slot\n console.debug('[Panthera Black Box] Ad slot initialized:', slot.id);\n }\n\n /**\n * Start telemetry collection\n */\n private startTelemetry(): void {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Collect initial telemetry\n this.sendTelemetry('page_view', {\n url: window.location.href,\n referrer: document.referrer,\n });\n\n // Listen for user interactions (if configured)\n if (typeof window !== 'undefined') {\n // Throttled event listeners for performance\n let interactionTimeout: number | null = null;\n \n const sendInteraction = () => {\n if (interactionTimeout) return;\n interactionTimeout = window.setTimeout(() => {\n this.sendTelemetry('interaction', {\n timestamp: new Date().toISOString(),\n });\n interactionTimeout = null;\n }, 1000);\n };\n\n // Only track if explicitly configured\n document.addEventListener('click', sendInteraction, { passive: true });\n document.addEventListener('scroll', sendInteraction, { passive: true });\n }\n\n // Start periodic telemetry if enabled\n const periodicConfig = this.config.panthera_blackbox.telemetry.periodic;\n if (periodicConfig?.enabled && typeof window !== 'undefined') {\n const intervalMs = periodicConfig.intervalMs || 300000; // Default 5 minutes\n \n // Clear any existing interval\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n }\n\n // Start periodic telemetry\n this.periodicIntervalId = window.setInterval(() => {\n this.sendPeriodicTelemetry();\n }, intervalMs);\n\n // Set up cleanup on page unload\n this.setupCleanup();\n }\n }\n\n /**\n * Set up cleanup handlers for page unload\n */\n private setupCleanup(): void {\n if (typeof window === 'undefined') return;\n\n // Clean up interval on page unload\n const cleanup = () => {\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n };\n\n // Use beforeunload for better reliability\n window.addEventListener('beforeunload', cleanup);\n \n // Also use pagehide for mobile browsers\n window.addEventListener('pagehide', cleanup);\n\n // Clean up on visibility change (when tab becomes hidden)\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // Send final periodic telemetry before cleanup\n this.sendPeriodicTelemetry();\n }\n });\n }\n\n /**\n * Send telemetry event\n */\n private async sendTelemetry(eventType: string, data: Record<string, unknown>): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Track state for periodic telemetry\n if (eventType === 'page_view') {\n this.periodicState.pageViews++;\n if (typeof window !== 'undefined') {\n this.periodicState.pageHistory.push({\n url: window.location.href,\n timestamp: Date.now(),\n });\n // Keep only last 50 pages to prevent memory leaks\n if (this.periodicState.pageHistory.length > 50) {\n this.periodicState.pageHistory.shift();\n }\n }\n } else if (eventType === 'interaction') {\n this.periodicState.interactions++;\n } else if (eventType === 'search') {\n this.periodicState.searches++;\n const searchQuery = (data.search as { query?: string })?.query || \n (data.context as { search_query?: string })?.search_query;\n if (searchQuery) {\n this.periodicState.searchQueries.push({\n query: searchQuery,\n timestamp: Date.now(),\n });\n // Keep only last 50 searches to prevent memory leaks\n if (this.periodicState.searchQueries.length > 50) {\n this.periodicState.searchQueries.shift();\n }\n }\n }\n\n const { page, search, ...context } = data;\n const sessionId = this.getSessionId();\n const isPageView = eventType === 'page_view';\n const pagePayload =\n page ||\n (isPageView && typeof window !== 'undefined'\n ? {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n }\n : undefined);\n const searchPayload: TelemetryEvent['search'] = eventType === 'search' && search \n ? (search as TelemetryEvent['search'])\n : undefined;\n\n const contextPayload = { event_type: eventType, ...context };\n\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: (['page_view', 'interaction', 'search'].includes(eventType)\n ? eventType\n : 'custom') as TelemetryEvent['event_type'],\n session_id: sessionId,\n page: pagePayload,\n search: searchPayload,\n context: Object.keys(contextPayload).length > 0 ? contextPayload : undefined,\n metrics: this.collectMetrics(),\n };\n\n try {\n // Use sendBeacon for reliability (doesn't block page unload)\n // Note: sendBeacon requires Blob with Content-Type for JSON\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n // Fallback to fetch\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n } catch (error) {\n // Fail silently - telemetry should never break the site\n console.debug('[Panthera Black Box] Telemetry failed:', error);\n }\n }\n\n /**\n * Send periodic telemetry event with aggregated metrics\n */\n private async sendPeriodicTelemetry(): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n if (typeof window === 'undefined') return;\n\n try {\n const now = Date.now();\n const timeSinceLastSend = now - this.periodicState.lastSent;\n\n // Collect comprehensive metrics\n const metrics = this.collectMetrics();\n\n // Calculate aggregated data for confusion detection\n const repeatedSearches = this.detectRepeatedSearches();\n const deadEnds = this.detectDeadEnds();\n const dropOffs = this.detectDropOffs();\n\n // Detect content gaps\n const contentGaps = this.detectContentGaps();\n const funnelStage = this.detectFunnelStage();\n const intent = this.detectIntent();\n\n // Build comprehensive context with all data needed for dashboard\n const context: Record<string, unknown> = {\n periodic: true,\n timeSinceLastSend,\n // Aggregated counts for telemetry dashboard\n aggregated: {\n pageViews: this.periodicState.pageViews,\n interactions: this.periodicState.interactions,\n searches: this.periodicState.searches,\n },\n // Confusion signals for confusion dashboard\n confusion: {\n repeatedSearches: repeatedSearches.length,\n deadEnds: deadEnds.length,\n dropOffs: dropOffs.length,\n // Include detailed evidence for dashboard processing\n repeatedSearchesDetail: repeatedSearches.slice(0, 10),\n deadEndsDetail: deadEnds.slice(0, 10),\n dropOffsDetail: dropOffs.slice(0, 10),\n },\n // Coverage data for coverage dashboard\n coverage: {\n contentGaps: contentGaps.length,\n contentGapsDetail: contentGaps,\n funnelStage,\n intent,\n // Include page metadata for content inventory\n pageMetadata: {\n hasJsonLd: document.querySelectorAll('script[type=\"application/ld+json\"]').length > 0,\n h1Count: document.querySelectorAll('h1').length,\n h2Count: document.querySelectorAll('h2').length,\n textLength: document.body?.textContent?.length || 0,\n },\n },\n // Intent for intent matching\n intent,\n // Funnel stage for coverage analysis\n funnelStage,\n };\n\n // Send periodic event as custom type but with page_view-like structure for dashboard compatibility\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'custom',\n session_id: this.getSessionId(),\n // Always include current page info so dashboard can track page views\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context,\n metrics,\n };\n\n // Send periodic event\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n\n // Also send a page_view event to ensure telemetry dashboard counts this as a view\n // This ensures the dashboard sees activity even during idle periods and counts page views correctly\n const pageViewEvent: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'page_view',\n session_id: this.getSessionId(),\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context: {\n periodic: true,\n heartbeat: true,\n aggregatedMetrics: metrics,\n // Include aggregated counts in context for dashboard processing\n aggregatedPageViews: this.periodicState.pageViews,\n aggregatedInteractions: this.periodicState.interactions,\n aggregatedSearches: this.periodicState.searches,\n },\n metrics,\n };\n\n // Send heartbeat page view\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(pageViewEvent)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(pageViewEvent),\n keepalive: true,\n });\n }\n\n // Reset state after successful send\n this.periodicState.pageViews = 0;\n this.periodicState.interactions = 0;\n this.periodicState.searches = 0;\n this.periodicState.lastSent = now;\n } catch (error) {\n // Fail silently - periodic telemetry should never break the site\n console.debug('[Panthera Black Box] Periodic telemetry failed:', error);\n }\n }\n\n /**\n * Detect repeated searches in current session\n */\n private detectRepeatedSearches(): Array<{ query: string; count: number }> {\n const queryCounts = new Map<string, number>();\n this.periodicState.searchQueries.forEach(({ query }) => {\n queryCounts.set(query, (queryCounts.get(query) || 0) + 1);\n });\n\n const repeated: Array<{ query: string; count: number }> = [];\n queryCounts.forEach((count, query) => {\n if (count > 1) {\n repeated.push({ query, count });\n }\n });\n\n return repeated;\n }\n\n /**\n * Detect dead ends (pages with no navigation after threshold)\n */\n private detectDeadEnds(): Array<{ url: string; at: string }> {\n const deadEnds: Array<{ url: string; at: string }> = [];\n const DEAD_END_THRESHOLD_MS = 60_000; // 1 minute\n\n if (this.periodicState.pageHistory.length < 2) return deadEnds;\n\n for (let i = 0; i < this.periodicState.pageHistory.length - 1; i++) {\n const current = this.periodicState.pageHistory[i];\n const next = this.periodicState.pageHistory[i + 1];\n const timeDiff = next.timestamp - current.timestamp;\n\n if (timeDiff > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: current.url,\n at: new Date(current.timestamp).toISOString(),\n });\n }\n }\n\n // Check if last page is a dead end (no navigation after threshold)\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const timeSinceLastPage = Date.now() - lastPage.timestamp;\n if (timeSinceLastPage > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: lastPage.url,\n at: new Date(lastPage.timestamp).toISOString(),\n });\n }\n\n return deadEnds;\n }\n\n /**\n * Detect drop-offs (sessions with minimal activity)\n */\n private detectDropOffs(): Array<{ sessionId: string; lastEvent: string }> {\n const dropOffs: Array<{ sessionId: string; lastEvent: string }> = [];\n \n // If we have very few events, consider it a drop-off\n const totalEvents = this.periodicState.pageViews + this.periodicState.interactions + this.periodicState.searches;\n if (totalEvents <= 2 && this.periodicState.pageHistory.length > 0) {\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const sessionId = this.getSessionId();\n if (sessionId) {\n dropOffs.push({\n sessionId,\n lastEvent: new Date(lastPage.timestamp).toISOString(),\n });\n }\n }\n\n return dropOffs;\n }\n\n /**\n * Detect content gaps (missing what/who/how/trust sections)\n */\n private detectContentGaps(): string[] {\n if (typeof document === 'undefined') return [];\n \n const gaps: string[] = [];\n const body = document.body;\n if (!body) return gaps;\n\n const text = body.textContent?.toLowerCase() || '';\n const contentConfig = this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;\n\n // Check for what/who/how/trust content\n if (!contentConfig?.what && !text.includes('what') && !text.includes('do')) {\n gaps.push('what');\n }\n if (!contentConfig?.who && !text.includes('who') && !text.includes('for')) {\n gaps.push('who');\n }\n if (!contentConfig?.how && !text.includes('how') && !text.includes('work')) {\n gaps.push('how');\n }\n if (!contentConfig?.trust && !text.includes('trust') && !text.includes('certified')) {\n gaps.push('trust');\n }\n\n return gaps;\n }\n\n /**\n * Detect funnel stage from URL and content\n */\n private detectFunnelStage(): string {\n if (typeof window === 'undefined') return 'awareness';\n\n const url = window.location.href.toLowerCase();\n const path = window.location.pathname.toLowerCase();\n\n if (url.includes('pricing') || url.includes('signup') || url.includes('checkout') || path.includes('pricing')) {\n return 'decision';\n }\n if (url.includes('docs') || url.includes('case-study') || url.includes('guides') || path.includes('docs')) {\n return 'consideration';\n }\n if (url.includes('support') || url.includes('account') || url.includes('dashboard') || path.includes('account')) {\n return 'retention';\n }\n return 'awareness';\n }\n\n /**\n * Detect intent from page content\n */\n private detectIntent(): string {\n if (typeof window === 'undefined') return 'general';\n\n const url = window.location.href.toLowerCase();\n const title = document.title?.toLowerCase() || '';\n const combined = `${url} ${title}`;\n\n if (combined.includes('pricing')) return 'pricing';\n if (combined.includes('demo')) return 'demo';\n if (combined.includes('docs')) return 'docs';\n if (combined.includes('case study')) return 'case-study';\n return 'general';\n }\n\n /**\n * Collect metrics based on configured keys\n */\n private collectMetrics(): Record<string, number> {\n const metrics: Record<string, number> = {};\n const keys = this.config?.panthera_blackbox.telemetry?.keys || [];\n\n // Collect real metrics from page state\n keys.forEach((key) => {\n if (key.startsWith('ai.')) {\n // AI search metrics\n if (key === 'ai.schemaCompleteness') {\n metrics[key] = this.calculateSchemaCompleteness();\n } else if (key === 'ai.structuredDataQuality') {\n metrics[key] = this.calculateStructuredDataQuality();\n } else if (key === 'ai.authoritySignals') {\n metrics[key] = this.calculateAuthoritySignals();\n } else if (key === 'ai.searchVisibility') {\n metrics[key] = this.calculateSearchVisibility();\n } else {\n // Default AI metrics\n metrics[key] = 0.6 + Math.random() * 0.35;\n }\n } else if (key.startsWith('ts.')) {\n // TruthSeeker metrics\n if (key === 'ts.authority') {\n metrics[key] = this.calculateAuthorityScore();\n } else {\n // Default TruthSeeker metrics\n metrics[key] = 0.5 + Math.random() * 0.4;\n }\n } else {\n // Other metrics - default to 0\n metrics[key] = 0;\n }\n });\n\n return metrics;\n }\n\n /**\n * Calculate schema completeness score (0-1)\n * Based on presence and count of JSON-LD schemas\n */\n private calculateSchemaCompleteness(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const pantheraSchemas = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n \n // Base score: 0.3 for any schema, 0.5 for Panthera schemas\n let score = 0;\n if (schemas.length > 0) score = 0.3;\n if (pantheraSchemas.length > 0) score = 0.5;\n \n // Bonus for multiple schema types\n const schemaTypes = new Set<string>();\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (content) {\n const parsed = JSON.parse(content);\n if (parsed['@type']) {\n schemaTypes.add(parsed['@type']);\n }\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n \n // Each unique schema type adds 0.1 (max 0.5 bonus)\n score += Math.min(0.5, schemaTypes.size * 0.1);\n \n return Math.min(1, score);\n }\n\n /**\n * Calculate structured data quality score (0-1)\n * Based on JSON-LD schema validity and completeness\n */\n private calculateStructuredDataQuality(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length === 0) return 0;\n\n let validCount = 0;\n let totalFields = 0;\n let requiredFields = 0;\n\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (!content) return;\n \n const parsed = JSON.parse(content);\n if (!parsed['@context'] || !parsed['@type']) {\n return; // Invalid schema\n }\n\n validCount++;\n \n // Count fields\n const fields = Object.keys(parsed);\n totalFields += fields.length;\n \n // Check for required fields based on type\n const type = parsed['@type'];\n if (type === 'Organization' && parsed.name && parsed.url) {\n requiredFields += 2;\n } else if (type === 'Product' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'Service' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'FAQPage' && Array.isArray(parsed.mainEntity)) {\n requiredFields += 1;\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n\n if (validCount === 0) return 0;\n\n // Quality score: 0.5 base for valid schemas, 0.3 for field richness, 0.2 for required fields\n const validityScore = Math.min(0.5, validCount * 0.25);\n const richnessScore = Math.min(0.3, (totalFields / validCount) / 10);\n const completenessScore = Math.min(0.2, requiredFields * 0.1);\n\n return Math.min(1, validityScore + richnessScore + completenessScore);\n }\n\n /**\n * Calculate authority signals score (0-1)\n * Based on trust signals, reviews, certifications, etc.\n */\n private calculateAuthoritySignals(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n const body = document.body;\n if (!body) return 0;\n\n const text = body.textContent?.toLowerCase() || '';\n \n // Check for trust signals\n const trustKeywords = ['certified', 'award', 'trusted', 'verified', 'accredited', 'licensed'];\n const foundKeywords = trustKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundKeywords.length * 0.05);\n\n // Check for social proof (reviews, testimonials, ratings)\n const reviewKeywords = ['review', 'testimonial', 'rating', 'star', 'customer'];\n const foundReviews = reviewKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundReviews.length * 0.05);\n\n // Check for authority grove config\n if (this.config?.panthera_blackbox.authority_grove?.node?.sameAs) {\n const sameAsCount = this.config.panthera_blackbox.authority_grove.node.sameAs.length;\n score += Math.min(0.4, sameAsCount * 0.1);\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate search visibility score (0-1)\n * Based on meta tags, structured data, content quality\n */\n private calculateSearchVisibility(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n\n // Meta description (0.2)\n const metaDesc = document.querySelector('meta[name=\"description\"]');\n if (metaDesc && metaDesc.getAttribute('content') && metaDesc.getAttribute('content')!.length > 50) {\n score += 0.2;\n }\n\n // Title tag (0.2)\n const title = document.querySelector('title');\n if (title && title.textContent && title.textContent.length > 30) {\n score += 0.2;\n }\n\n // H1 tag (0.2)\n const h1 = document.querySelector('h1');\n if (h1 && h1.textContent) {\n score += 0.2;\n }\n\n // Structured data (0.2)\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length > 0) {\n score += 0.2;\n }\n\n // Content depth (0.2)\n const h2s = document.querySelectorAll('h2');\n const textLength = document.body?.textContent?.length || 0;\n if (h2s.length >= 3 && textLength > 1000) {\n score += 0.2;\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate authority score (0-1)\n * Based on authority grove config and sameAs links\n */\n private calculateAuthorityScore(): number {\n if (!this.config?.panthera_blackbox.authority_grove?.node) {\n return 0.5; // Default score if no config\n }\n\n const node = this.config.panthera_blackbox.authority_grove.node;\n let score = 0.3; // Base score\n\n // SameAs links boost authority\n if (node.sameAs && node.sameAs.length > 0) {\n score += Math.min(0.4, node.sameAs.length * 0.1);\n }\n\n // Keywords boost authority\n if (node.keywords && node.keywords.length > 0) {\n score += Math.min(0.3, node.keywords.length * 0.05);\n }\n\n return Math.min(1, score);\n }\n\n private getSessionId(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const storage = window.sessionStorage;\n if (!storage) return undefined;\n\n const key = 'panthera_session_id';\n const tsKey = 'panthera_session_ts';\n const now = Date.now();\n const ttlMs = 30 * 60 * 1000;\n\n const existing = storage.getItem(key);\n const existingTs = Number(storage.getItem(tsKey) || 0);\n if (existing && existingTs && now - existingTs < ttlMs) {\n storage.setItem(tsKey, String(now));\n return existing;\n }\n\n const newId =\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${now}-${Math.random().toString(16).slice(2)}`;\n storage.setItem(key, newId);\n storage.setItem(tsKey, String(now));\n return newId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): BlackBoxConfig | null {\n return this.config;\n }\n\n /**\n * Reload configuration\n */\n async reload(): Promise<void> {\n // Clean up periodic telemetry interval if running\n if (this.periodicIntervalId !== null && typeof window !== 'undefined') {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n\n // Reset periodic state\n this.periodicState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n this.initialized = false;\n await this.init();\n }\n}\n\n// Auto-initialize if script tag has data attributes\n(function () {\n if (typeof window === 'undefined') return;\n\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n\n const configUrl = script.getAttribute('data-config-url');\n const telemetryUrl = script.getAttribute('data-telemetry-url');\n const siteId = script.getAttribute('data-site-id');\n\n if (!configUrl || !telemetryUrl || !siteId) {\n console.warn('[Panthera Black Box] Missing required data attributes');\n return;\n }\n\n const blackBox = new PantheraBlackBox({\n configUrl,\n telemetryUrl,\n siteId,\n });\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => blackBox.init());\n } else {\n blackBox.init();\n }\n\n // Expose globally for manual control (optional)\n (window as unknown as { PantheraBlackBox: typeof PantheraBlackBox }).PantheraBlackBox = PantheraBlackBox;\n (window as unknown as { panthera: PantheraBlackBox }).panthera = blackBox;\n})();\n\nexport { PantheraBlackBox };\nexport default PantheraBlackBox;\n"],"mappings":"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","names":["runtime_exports","__export","PantheraBlackBox","runtime_default","options","response","data","error","config","el","tier","schema","index","script","schemas","baseSchema","product","service","faqs","faq","seoConfig","meta","canonical","contentConfig","body","aiContentSection","whatDiv","whoDiv","howDiv","trustDiv","depthConfig","existingH2s","targetH2Count","existingTextLength","neededTextLength","depthSection","addedTextLength","neededH2s","i","h2","paragraphText","charsPerParagraph","paragraphsNeeded","p","paragraph","structureConfig","h1","firstChild","title","currentTitle","minLength","newTitle","newTitleElement","form","formElement","event","target","autofillData","fieldName","selector","field","slot","sanitizedId","escapedId","slotElement","interactionTimeout","sendInteraction","periodicConfig","intervalMs","cleanup","eventType","searchQuery","page","search","context","sessionId","pagePayload","searchPayload","contextPayload","blob","now","timeSinceLastSend","metrics","repeatedSearches","deadEnds","dropOffs","contentGaps","funnelStage","intent","pageViewEvent","queryCounts","query","repeated","count","current","lastPage","gaps","text","url","path","combined","key","pantheraSchemas","score","schemaTypes","content","parsed","validCount","totalFields","requiredFields","fields","type","validityScore","richnessScore","completenessScore","foundKeywords","keyword","foundReviews","sameAsCount","metaDesc","h2s","textLength","node","storage","tsKey","ttlMs","existing","existingTs","newId","configUrl","telemetryUrl","siteId","blackBox","runtime_default","PantheraBlackBox"]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Panthera Black Box Runtime\n * \n * AI Search Optimization Runtime - Optimizes websites for AI search engines\n * (ChatGPT, Perplexity, Claude, etc.) by injecting structured data and building\n * authority signals that AI models can understand and prioritize.\n * \n * Key Features:\n * - JSON-LD schema injection for AI model comprehension\n * - Authority Grove integration for trust signals\n * - TruthSeeker integration for factual accuracy\n * - Telemetry for AI search visibility tracking\n * \n * Designed to be <10KB gzipped when compiled.\n * \n * Safety: No eval(), no Function(), no arbitrary code execution.\n * All operations are declarative JSON transformations only.\n */\n\ninterface AutoFillForm {\n selector: string;\n map: Record<string, string>;\n}\n\ninterface AutofillConfig {\n enabled?: boolean;\n forms?: AutoFillForm[];\n}\n\ninterface AdSlot {\n id: string;\n contexts: string[];\n}\n\ninterface AdsConfig {\n slots?: AdSlot[];\n}\n\ninterface AuthorityGroveNode {\n sameAs?: string[];\n keywords?: string[];\n}\n\ninterface AuthorityGroveConfig {\n node?: AuthorityGroveNode;\n}\n\ninterface ProductConfig {\n name?: string;\n description?: string;\n}\n\ninterface ServiceConfig {\n name?: string;\n description?: string;\n}\n\ninterface FaqConfig {\n question: string;\n answer: string;\n}\n\ninterface SEOEnhancements {\n meta_description?: string;\n canonical_enabled?: boolean;\n content_enhancements?: {\n enabled: boolean;\n what?: string;\n who?: string;\n how?: string;\n trust?: string;\n };\n content_depth?: {\n enabled: boolean;\n min_h2_count?: number;\n h2_templates?: string[];\n content_templates?: string[];\n default_content?: string;\n };\n structure_enhancements?: {\n inject_h1_if_missing?: boolean;\n h1_text?: string;\n enhance_title?: boolean;\n min_title_length?: number;\n title_template?: string;\n };\n}\n\ninterface BlackBoxConfig {\n panthera_blackbox: {\n version: string;\n site: {\n domain: string;\n brand: string;\n verticals: string[];\n geo: string[];\n };\n telemetry: {\n emit: boolean;\n keys: string[];\n periodic?: {\n enabled: boolean;\n intervalMs?: number; // default 300000 (5 minutes)\n };\n };\n tier?: 'bronze' | 'silver' | 'gold';\n autofill?: AutofillConfig;\n ads?: AdsConfig;\n authority_grove?: AuthorityGroveConfig;\n products?: ProductConfig[];\n services?: ServiceConfig[];\n faqs?: FaqConfig[];\n seo_enhancements?: SEOEnhancements;\n [key: string]: unknown;\n };\n}\n\ninterface TelemetryEvent {\n schema: string;\n tenant: string;\n timestamp: string;\n source: 'blackbox';\n event_type: 'page_view' | 'interaction' | 'search' | 'custom';\n session_id?: string;\n page?: {\n url?: string;\n path?: string;\n title?: string;\n };\n search?: {\n query?: string;\n results_count?: number;\n selected_result?: string;\n };\n context?: Record<string, unknown>;\n metrics: Record<string, number>;\n}\n\ninterface PeriodicTelemetryState {\n pageViews: number;\n interactions: number;\n searches: number;\n lastSent: number;\n pageHistory: Array<{ url: string; timestamp: number }>;\n searchQueries: Array<{ query: string; timestamp: number }>;\n}\n\nclass PantheraBlackBox {\n private config: BlackBoxConfig | null = null;\n private configUrl: string;\n private telemetryUrl: string;\n private _siteId: string;\n private initialized = false;\n private periodicIntervalId: number | null = null;\n private heartbeatIntervalId: number | null = null;\n private memorySessionId?: string;\n private memorySessionTs?: number;\n private periodicState: PeriodicTelemetryState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n constructor(options: { configUrl: string; telemetryUrl: string; siteId: string }) {\n this.configUrl = options.configUrl;\n this.telemetryUrl = options.telemetryUrl;\n this._siteId = options.siteId;\n // siteId stored for potential future use (e.g., telemetry context)\n void this._siteId;\n }\n\n /**\n * Initialize the Black Box by loading configuration\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n const response = await fetch(this.configUrl, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n },\n cache: 'no-cache',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to load config: ${response.status}`);\n }\n\n const data = await response.json();\n \n // Basic validation - ensure it has the expected structure\n if (!data.panthera_blackbox || !data.panthera_blackbox.site) {\n throw new Error('Invalid config structure');\n }\n\n this.config = data as BlackBoxConfig;\n this.initialized = true;\n\n // Apply configuration if needed\n this.applyConfig();\n\n // Start telemetry if enabled\n if (this.config.panthera_blackbox.telemetry?.emit) {\n this.startTelemetry();\n }\n } catch (error) {\n console.error('[Panthera Black Box] Initialization failed:', error);\n \n // Fail silently in production, but log for debugging\n }\n }\n\n /**\n * Apply configuration to the page (declarative transformations only)\n */\n private applyConfig(): void {\n if (!this.config) return;\n\n const config = this.config.panthera_blackbox;\n \n // Inject JSON-LD schema if configured\n this.injectSchema(config);\n \n // Inject meta tags and canonical\n this.injectMetaTags(config);\n \n // Inject structure enhancements (H1, title)\n this.injectStructureEnhancements(config);\n \n // Inject content enhancements (AI Readiness)\n this.injectContentEnhancements(config);\n \n // Inject content depth (H2, content blocks)\n this.injectContentDepth(config);\n \n // Initialize AutoFill if enabled\n if (config.autofill?.enabled && config.autofill.forms) {\n this.initializeAutoFill(config);\n }\n \n // Initialize ad slots if configured\n if (config.ads?.slots) {\n this.initializeAdSlots(config);\n }\n }\n\n /**\n * Inject JSON-LD schema\n */\n private injectSchema(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n // Remove existing Panthera schemas if present\n const existing = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n existing.forEach(el => el.remove());\n \n // Get tier from config (if available) or default to bronze\n const tier = config.tier ?? 'bronze';\n \n // Generate schemas based on tier\n const schemas = this.generateSchemasForTier(config, tier);\n \n // Inject all schemas\n schemas.forEach((schema, index) => {\n const script = document.createElement('script');\n script.type = 'application/ld+json';\n script.setAttribute('data-panthera', 'true');\n script.setAttribute('data-schema-index', index.toString());\n script.textContent = JSON.stringify(schema);\n document.head.appendChild(script);\n });\n }\n\n /**\n * Generate schemas based on tier\n */\n private generateSchemasForTier(\n config: BlackBoxConfig['panthera_blackbox'],\n tier: 'bronze' | 'silver' | 'gold'\n ): unknown[] {\n const schemas: unknown[] = [];\n \n // Base Organization schema (all tiers)\n const baseSchema = {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n };\n \n // Add authority grove data if available\n if (config.authority_grove?.node) {\n (baseSchema as Record<string, unknown>).sameAs = config.authority_grove.node.sameAs;\n (baseSchema as Record<string, unknown>).keywords = config.authority_grove.node.keywords;\n }\n \n schemas.push(baseSchema);\n \n // Silver and Gold tiers get additional schemas\n if (tier === 'silver' || tier === 'gold') {\n // Add Product schema if product data exists in config\n if (config.products) {\n const products = config.products;\n products.forEach((product) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name || config.site.brand,\n description: product.description,\n brand: {\n '@type': 'Brand',\n name: config.site.brand,\n },\n url: `https://${config.site.domain}`,\n });\n });\n }\n \n // Add Service schema if service data exists\n if (config.services) {\n const services = config.services;\n services.forEach((service) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Service',\n name: service.name || config.site.brand,\n description: service.description,\n provider: {\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n },\n });\n });\n }\n \n // Add LocalBusiness schema if geo data exists\n if (config.site.geo && config.site.geo.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'LocalBusiness',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n areaServed: config.site.geo,\n });\n }\n \n // Add FAQ schema if FAQ data exists\n if (config.faqs) {\n const faqs = config.faqs;\n if (faqs.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: faqs.map(faq => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n });\n }\n }\n }\n \n return schemas;\n }\n\n /**\n * Inject meta tags and canonical tag\n */\n private injectMetaTags(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const seoConfig = config.seo_enhancements;\n if (!seoConfig) return;\n \n // Inject meta description if missing\n if (seoConfig.meta_description) {\n const existingMeta = document.querySelector('meta[name=\"description\"]');\n if (!existingMeta) {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'description');\n meta.setAttribute('content', seoConfig.meta_description);\n meta.setAttribute('data-panthera', 'true');\n document.head.appendChild(meta);\n }\n }\n \n // Inject canonical tag if enabled and missing\n if (seoConfig.canonical_enabled) {\n const existingCanonical = document.querySelector('link[rel=\"canonical\"]');\n if (!existingCanonical) {\n const canonical = document.createElement('link');\n canonical.setAttribute('rel', 'canonical');\n canonical.setAttribute('href', window.location.href);\n canonical.setAttribute('data-panthera', 'true');\n document.head.appendChild(canonical);\n }\n }\n }\n\n /**\n * Inject content enhancements for AI Readiness\n */\n private injectContentEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const contentConfig = config.seo_enhancements?.content_enhancements;\n if (!contentConfig || !contentConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Create hidden content section for AI models (aria-hidden but readable by crawlers)\n const aiContentSection = document.createElement('section');\n aiContentSection.setAttribute('data-panthera', 'true');\n aiContentSection.setAttribute('data-panthera-type', 'ai-readiness');\n aiContentSection.setAttribute('aria-hidden', 'true');\n aiContentSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n // Add \"what you do\" content\n if (contentConfig.what) {\n const whatDiv = document.createElement('div');\n whatDiv.textContent = contentConfig.what;\n aiContentSection.appendChild(whatDiv);\n }\n \n // Add \"who it's for\" content\n if (contentConfig.who) {\n const whoDiv = document.createElement('div');\n whoDiv.textContent = contentConfig.who;\n aiContentSection.appendChild(whoDiv);\n }\n \n // Add \"how it works\" content\n if (contentConfig.how) {\n const howDiv = document.createElement('div');\n howDiv.textContent = contentConfig.how;\n aiContentSection.appendChild(howDiv);\n }\n \n // Add trust signals\n if (contentConfig.trust) {\n const trustDiv = document.createElement('div');\n trustDiv.textContent = contentConfig.trust;\n aiContentSection.appendChild(trustDiv);\n }\n \n body.appendChild(aiContentSection);\n }\n\n /**\n * Inject content depth enhancements (H2 headings and content blocks)\n */\n private injectContentDepth(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const depthConfig = config.seo_enhancements?.content_depth;\n if (!depthConfig || !depthConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Count existing H2s\n const existingH2s = body.querySelectorAll('h2').length;\n const targetH2Count = depthConfig.min_h2_count || 6;\n \n // Calculate existing text length\n const existingText = body.textContent || '';\n const existingTextLength = existingText.length;\n const targetTextLength = 6000; // Target for max score\n const neededTextLength = Math.max(0, targetTextLength - existingTextLength);\n \n // Create hidden content section with H2 headings and content\n const depthSection = document.createElement('section');\n depthSection.setAttribute('data-panthera', 'true');\n depthSection.setAttribute('data-panthera-type', 'content-depth');\n depthSection.setAttribute('aria-hidden', 'true');\n depthSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n let addedTextLength = 0;\n const neededH2s = Math.max(0, targetH2Count - existingH2s);\n \n // Generate H2 headings with content\n for (let i = 0; i < neededH2s || addedTextLength < neededTextLength; i++) {\n const h2 = document.createElement('h2');\n h2.textContent = depthConfig.h2_templates?.[i] || `Section ${i + 1}`;\n depthSection.appendChild(h2);\n addedTextLength += h2.textContent.length;\n \n // Add paragraph content for text length\n // Generate enough paragraphs to reach target text length\n const paragraphText = depthConfig.content_templates?.[i] || depthConfig.default_content || \n 'This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.';\n \n // Add multiple paragraphs if needed to reach target length\n const charsPerParagraph = paragraphText.length;\n const paragraphsNeeded = Math.ceil((neededTextLength - addedTextLength) / charsPerParagraph) || 1;\n \n for (let p = 0; p < Math.max(1, paragraphsNeeded) && addedTextLength < neededTextLength; p++) {\n const paragraph = document.createElement('p');\n paragraph.textContent = paragraphText;\n depthSection.appendChild(paragraph);\n addedTextLength += paragraphText.length;\n }\n }\n \n if (depthSection.children.length > 0) {\n body.appendChild(depthSection);\n }\n }\n\n /**\n * Inject structure enhancements (H1 and title tags)\n */\n private injectStructureEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const structureConfig = config.seo_enhancements?.structure_enhancements;\n if (!structureConfig) return;\n \n // Inject H1 if missing\n if (structureConfig.inject_h1_if_missing) {\n const existingH1 = document.querySelector('h1');\n if (!existingH1 && structureConfig.h1_text) {\n const body = document.body;\n if (body) {\n const h1 = document.createElement('h1');\n h1.textContent = structureConfig.h1_text;\n h1.setAttribute('data-panthera', 'true');\n // Insert at beginning of body or after first element\n const firstChild = body.firstElementChild;\n if (firstChild) {\n body.insertBefore(h1, firstChild);\n } else {\n body.appendChild(h1);\n }\n }\n }\n }\n \n // Enhance title tag if too short or missing\n if (structureConfig.enhance_title) {\n const title = document.querySelector('title');\n const currentTitle = title?.textContent || '';\n const minLength = structureConfig.min_title_length || 30;\n \n if (currentTitle.length < minLength && structureConfig.title_template) {\n const newTitle = structureConfig.title_template.replace('{brand}', config.site.brand);\n if (title) {\n title.textContent = newTitle;\n title.setAttribute('data-panthera-enhanced', 'true');\n } else {\n const newTitleElement = document.createElement('title');\n newTitleElement.textContent = newTitle;\n newTitleElement.setAttribute('data-panthera', 'true');\n document.head.appendChild(newTitleElement);\n }\n }\n }\n }\n\n /**\n * Initialize AutoFill for forms\n */\n private initializeAutoFill(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.autofill?.forms) return;\n \n config.autofill.forms.forEach((form: AutoFillForm) => {\n const formElement = document.querySelector(form.selector);\n if (formElement) {\n // Store form config for later use\n (formElement as HTMLElement & { pantheraAutoFill?: typeof form }).pantheraAutoFill = form;\n \n // Listen for first field focus to trigger AutoFill\n formElement.addEventListener('focusin', (event: Event) => {\n const target = event.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n this.triggerAutoFill(form);\n }\n }, { once: true });\n }\n });\n }\n\n /**\n * Trigger AutoFill for a form\n */\n private async triggerAutoFill(form: { selector: string; map: Record<string, string> }): Promise<void> {\n // In production, this would fetch CFP data from API\n // For now, use placeholder data\n const autofillData: Record<string, string> = {};\n \n // Apply AutoFill to form fields\n const formElement = document.querySelector(form.selector);\n if (!formElement) return;\n \n for (const [fieldName, selector] of Object.entries(form.map)) {\n const field = formElement.querySelector(selector) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n if (field && autofillData[fieldName]) {\n field.value = autofillData[fieldName];\n field.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n\n /**\n * Initialize ad slots\n */\n private initializeAdSlots(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.ads?.slots) return;\n \n config.ads.slots.forEach((slot: AdSlot) => {\n // Sanitize slot ID - aggressively remove quotes and ensure it's a valid CSS selector\n let sanitizedId = String(slot.id || '').trim();\n // Remove all quotes (single and double) from anywhere in the string\n sanitizedId = sanitizedId.replace(/[\"']/g, '');\n // Remove any remaining whitespace\n sanitizedId = sanitizedId.trim();\n \n if (!sanitizedId) {\n return;\n }\n \n try {\n // Escape special CSS characters in the ID (but NOT quotes - they should already be removed)\n // Only escape characters that need escaping in attribute selectors\n const escapedId = sanitizedId.replace(/[!\"#$%&'()*+,.\\/:;<=>?@[\\\\\\]^`{|}~]/g, '\\\\$&');\n // Double-check: ensure no quotes made it through\n if (escapedId.includes('\"') || escapedId.includes(\"'\")) {\n throw new Error(`Slot ID contains quotes after sanitization: ${escapedId}`);\n }\n \n const selector = `[data-ad-slot=\"${escapedId}\"]`;\n const slotElement = document.querySelector(selector);\n \n if (slotElement) {\n // Store slot config\n (slotElement as HTMLElement & { pantheraAdSlot?: typeof slot }).pantheraAdSlot = slot;\n \n // In production, this would load ad creative and track impressions\n this.loadAdCreative(slot);\n }\n } catch (error) {\n console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${slot.id}`, error);\n }\n });\n }\n\n /**\n * Load ad creative for a slot\n */\n private async loadAdCreative(slot: { id: string; contexts: string[] }): Promise<void> {\n // In production, this would:\n // 1. Fetch creative from API based on slot contexts\n // 2. Calculate CPM\n // 3. Render ad\n // 4. Track impression\n \n // Placeholder: just log the slot\n console.debug('[Panthera Black Box] Ad slot initialized:', slot.id);\n }\n\n /**\n * Start telemetry collection\n */\n private startTelemetry(): void {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Collect initial telemetry\n this.sendTelemetry('page_view', {\n url: window.location.href,\n referrer: document.referrer,\n });\n\n // Listen for user interactions (if configured)\n if (typeof window !== 'undefined') {\n // Throttled event listeners for performance\n let interactionTimeout: number | null = null;\n \n const sendInteraction = () => {\n if (interactionTimeout) return;\n interactionTimeout = window.setTimeout(() => {\n this.sendTelemetry('interaction', {\n timestamp: new Date().toISOString(),\n });\n interactionTimeout = null;\n }, 1000);\n };\n\n // Only track if explicitly configured\n document.addEventListener('click', sendInteraction, { passive: true });\n document.addEventListener('scroll', sendInteraction, { passive: true });\n }\n\n // Start periodic telemetry if enabled\n const periodicConfig = this.config.panthera_blackbox.telemetry.periodic;\n const periodicEnabled = periodicConfig?.enabled ?? true;\n if (periodicEnabled && typeof window !== 'undefined') {\n const intervalMs = periodicConfig?.intervalMs || 300000; // Default 5 minutes\n \n // Clear any existing interval\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n }\n\n // Start periodic telemetry\n this.periodicIntervalId = window.setInterval(() => {\n this.sendPeriodicTelemetry();\n }, intervalMs);\n\n // Send an initial periodic snapshot so dashboards populate immediately\n window.setTimeout(() => {\n this.sendPeriodicTelemetry();\n }, 1000);\n\n // Set up cleanup on page unload\n this.setupCleanup();\n } else if (typeof window !== 'undefined') {\n const intervalMs = 300000; // Default 5 minutes\n if (this.heartbeatIntervalId !== null) {\n window.clearInterval(this.heartbeatIntervalId);\n }\n this.heartbeatIntervalId = window.setInterval(() => {\n this.sendTelemetry('page_view', {\n heartbeat: true,\n timestamp: new Date().toISOString(),\n });\n }, intervalMs);\n this.setupCleanup();\n }\n }\n\n /**\n * Set up cleanup handlers for page unload\n */\n private setupCleanup(): void {\n if (typeof window === 'undefined') return;\n\n // Clean up interval on page unload\n const cleanup = () => {\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n if (this.heartbeatIntervalId !== null) {\n window.clearInterval(this.heartbeatIntervalId);\n this.heartbeatIntervalId = null;\n }\n };\n\n // Use beforeunload for better reliability\n window.addEventListener('beforeunload', cleanup);\n \n // Also use pagehide for mobile browsers\n window.addEventListener('pagehide', cleanup);\n\n // Clean up on visibility change (when tab becomes hidden)\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // Send final periodic telemetry before cleanup\n const periodicConfig = this.config?.panthera_blackbox.telemetry?.periodic;\n const periodicEnabled = periodicConfig?.enabled ?? true;\n if (periodicEnabled) {\n this.sendPeriodicTelemetry();\n } else {\n this.sendTelemetry('page_view', { heartbeat: true, timestamp: new Date().toISOString() });\n }\n }\n });\n }\n\n /**\n * Send telemetry event\n */\n private async sendTelemetry(eventType: string, data: Record<string, unknown>): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Track state for periodic telemetry\n if (eventType === 'page_view') {\n this.periodicState.pageViews++;\n if (typeof window !== 'undefined') {\n this.periodicState.pageHistory.push({\n url: window.location.href,\n timestamp: Date.now(),\n });\n // Keep only last 50 pages to prevent memory leaks\n if (this.periodicState.pageHistory.length > 50) {\n this.periodicState.pageHistory.shift();\n }\n }\n } else if (eventType === 'interaction') {\n this.periodicState.interactions++;\n } else if (eventType === 'search') {\n this.periodicState.searches++;\n const searchQuery = (data.search as { query?: string })?.query || \n (data.context as { search_query?: string })?.search_query;\n if (searchQuery) {\n this.periodicState.searchQueries.push({\n query: searchQuery,\n timestamp: Date.now(),\n });\n // Keep only last 50 searches to prevent memory leaks\n if (this.periodicState.searchQueries.length > 50) {\n this.periodicState.searchQueries.shift();\n }\n }\n }\n\n const { page, search, ...context } = data;\n const sessionId = this.getSessionId();\n const isPageView = eventType === 'page_view';\n const pagePayload =\n page ||\n (isPageView && typeof window !== 'undefined'\n ? {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n }\n : undefined);\n const contextQuery =\n (context.search_query as string | undefined) ||\n (context.query as string | undefined);\n const contextResults = context.results_count as number | undefined;\n const contextSelected = context.selected_result as string | undefined;\n const searchPayload: TelemetryEvent['search'] = eventType === 'search'\n ? (search as TelemetryEvent['search']) ||\n (contextQuery\n ? {\n query: contextQuery,\n results_count: contextResults,\n selected_result: contextSelected,\n }\n : undefined)\n : undefined;\n\n const contextPayload: Record<string, unknown> = { event_type: eventType, ...context };\n if (isPageView) {\n if (contextPayload.intent === undefined) {\n contextPayload.intent = this.detectIntent();\n }\n if (contextPayload.funnelStage === undefined) {\n contextPayload.funnelStage = this.detectFunnelStage();\n }\n }\n\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: (['page_view', 'interaction', 'search'].includes(eventType)\n ? eventType\n : 'custom') as TelemetryEvent['event_type'],\n session_id: sessionId,\n page: pagePayload,\n search: searchPayload,\n context: Object.keys(contextPayload).length > 0 ? contextPayload : undefined,\n metrics: this.collectMetrics(),\n };\n\n try {\n // Use sendBeacon for reliability (doesn't block page unload)\n // Note: sendBeacon requires Blob with Content-Type for JSON\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n // Fallback to fetch\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n } catch (error) {\n // Fail silently - telemetry should never break the site\n console.debug('[Panthera Black Box] Telemetry failed:', error);\n }\n }\n\n /**\n * Send periodic telemetry event with aggregated metrics\n */\n private async sendPeriodicTelemetry(): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n if (typeof window === 'undefined') return;\n\n try {\n const now = Date.now();\n const timeSinceLastSend = now - this.periodicState.lastSent;\n\n // Collect comprehensive metrics\n const metrics = this.collectMetrics();\n\n // Calculate aggregated data for confusion detection\n const repeatedSearches = this.detectRepeatedSearches();\n const deadEnds = this.detectDeadEnds();\n const dropOffs = this.detectDropOffs();\n\n // Detect content gaps\n const contentGaps = this.detectContentGaps();\n const funnelStage = this.detectFunnelStage();\n const intent = this.detectIntent();\n\n // Build comprehensive context with all data needed for dashboard\n const context: Record<string, unknown> = {\n periodic: true,\n timeSinceLastSend,\n // Aggregated counts for telemetry dashboard\n aggregated: {\n pageViews: this.periodicState.pageViews,\n interactions: this.periodicState.interactions,\n searches: this.periodicState.searches,\n },\n // Confusion signals for confusion dashboard\n confusion: {\n repeatedSearches: repeatedSearches.length,\n deadEnds: deadEnds.length,\n dropOffs: dropOffs.length,\n // Include detailed evidence for dashboard processing\n repeatedSearchesDetail: repeatedSearches.slice(0, 10),\n deadEndsDetail: deadEnds.slice(0, 10),\n dropOffsDetail: dropOffs.slice(0, 10),\n },\n // Coverage data for coverage dashboard\n coverage: {\n contentGaps: contentGaps.length,\n contentGapsDetail: contentGaps,\n funnelStage,\n intent,\n // Include page metadata for content inventory\n pageMetadata: {\n hasJsonLd: document.querySelectorAll('script[type=\"application/ld+json\"]').length > 0,\n h1Count: document.querySelectorAll('h1').length,\n h2Count: document.querySelectorAll('h2').length,\n textLength: document.body?.textContent?.length || 0,\n },\n },\n // Intent for intent matching\n intent,\n // Funnel stage for coverage analysis\n funnelStage,\n };\n\n // Send periodic event as custom type but with page_view-like structure for dashboard compatibility\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'custom',\n session_id: this.getSessionId(),\n // Always include current page info so dashboard can track page views\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context,\n metrics,\n };\n\n // Send periodic event\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n\n // Also send a page_view event to ensure telemetry dashboard counts this as a view\n // This ensures the dashboard sees activity even during idle periods and counts page views correctly\n const pageViewEvent: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'page_view',\n session_id: this.getSessionId(),\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context: {\n periodic: true,\n heartbeat: true,\n aggregatedMetrics: metrics,\n // Include aggregated counts in context for dashboard processing\n aggregatedPageViews: this.periodicState.pageViews,\n aggregatedInteractions: this.periodicState.interactions,\n aggregatedSearches: this.periodicState.searches,\n },\n metrics,\n };\n\n // Send heartbeat page view\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(pageViewEvent)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(pageViewEvent),\n keepalive: true,\n });\n }\n\n // Reset state after successful send\n this.periodicState.pageViews = 0;\n this.periodicState.interactions = 0;\n this.periodicState.searches = 0;\n this.periodicState.lastSent = now;\n } catch (error) {\n // Fail silently - periodic telemetry should never break the site\n console.debug('[Panthera Black Box] Periodic telemetry failed:', error);\n }\n }\n\n /**\n * Detect repeated searches in current session\n */\n private detectRepeatedSearches(): Array<{ query: string; count: number }> {\n const queryCounts = new Map<string, number>();\n this.periodicState.searchQueries.forEach(({ query }) => {\n queryCounts.set(query, (queryCounts.get(query) || 0) + 1);\n });\n\n const repeated: Array<{ query: string; count: number }> = [];\n queryCounts.forEach((count, query) => {\n if (count > 1) {\n repeated.push({ query, count });\n }\n });\n\n return repeated;\n }\n\n /**\n * Detect dead ends (pages with no navigation after threshold)\n */\n private detectDeadEnds(): Array<{ url: string; at: string }> {\n const deadEnds: Array<{ url: string; at: string }> = [];\n const DEAD_END_THRESHOLD_MS = 60_000; // 1 minute\n\n if (this.periodicState.pageHistory.length < 2) return deadEnds;\n\n for (let i = 0; i < this.periodicState.pageHistory.length - 1; i++) {\n const current = this.periodicState.pageHistory[i];\n const next = this.periodicState.pageHistory[i + 1];\n const timeDiff = next.timestamp - current.timestamp;\n\n if (timeDiff > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: current.url,\n at: new Date(current.timestamp).toISOString(),\n });\n }\n }\n\n // Check if last page is a dead end (no navigation after threshold)\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const timeSinceLastPage = Date.now() - lastPage.timestamp;\n if (timeSinceLastPage > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: lastPage.url,\n at: new Date(lastPage.timestamp).toISOString(),\n });\n }\n\n return deadEnds;\n }\n\n /**\n * Detect drop-offs (sessions with minimal activity)\n */\n private detectDropOffs(): Array<{ sessionId: string; lastEvent: string }> {\n const dropOffs: Array<{ sessionId: string; lastEvent: string }> = [];\n \n // If we have very few events, consider it a drop-off\n const totalEvents = this.periodicState.pageViews + this.periodicState.interactions + this.periodicState.searches;\n if (totalEvents <= 2 && this.periodicState.pageHistory.length > 0) {\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const sessionId = this.getSessionId();\n if (sessionId) {\n dropOffs.push({\n sessionId,\n lastEvent: new Date(lastPage.timestamp).toISOString(),\n });\n }\n }\n\n return dropOffs;\n }\n\n /**\n * Detect content gaps (missing what/who/how/trust sections)\n */\n private detectContentGaps(): string[] {\n if (typeof document === 'undefined') return [];\n \n const gaps: string[] = [];\n const body = document.body;\n if (!body) return gaps;\n\n const text = body.textContent?.toLowerCase() || '';\n const contentConfig = this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;\n\n // Check for what/who/how/trust content\n if (!contentConfig?.what && !text.includes('what') && !text.includes('do')) {\n gaps.push('what');\n }\n if (!contentConfig?.who && !text.includes('who') && !text.includes('for')) {\n gaps.push('who');\n }\n if (!contentConfig?.how && !text.includes('how') && !text.includes('work')) {\n gaps.push('how');\n }\n if (!contentConfig?.trust && !text.includes('trust') && !text.includes('certified')) {\n gaps.push('trust');\n }\n\n return gaps;\n }\n\n /**\n * Detect funnel stage from URL and content\n */\n private detectFunnelStage(): string {\n if (typeof window === 'undefined') return 'awareness';\n\n const url = window.location.href.toLowerCase();\n const path = window.location.pathname.toLowerCase();\n\n if (url.includes('pricing') || url.includes('signup') || url.includes('checkout') || path.includes('pricing')) {\n return 'decision';\n }\n if (url.includes('docs') || url.includes('case-study') || url.includes('guides') || path.includes('docs')) {\n return 'consideration';\n }\n if (url.includes('support') || url.includes('account') || url.includes('dashboard') || path.includes('account')) {\n return 'retention';\n }\n return 'awareness';\n }\n\n /**\n * Detect intent from page content\n */\n private detectIntent(): string {\n if (typeof window === 'undefined') return 'general';\n\n const url = window.location.href.toLowerCase();\n const title = document.title?.toLowerCase() || '';\n const combined = `${url} ${title}`;\n\n if (combined.includes('pricing')) return 'pricing';\n if (combined.includes('demo')) return 'demo';\n if (combined.includes('docs')) return 'docs';\n if (combined.includes('case study')) return 'case-study';\n return 'general';\n }\n\n /**\n * Collect metrics based on configured keys\n */\n private collectMetrics(): Record<string, number> {\n const metrics: Record<string, number> = {};\n const keys = this.config?.panthera_blackbox.telemetry?.keys || [];\n const requiredKeys = [\n 'ts.authority',\n 'ai.schemaCompleteness',\n 'ai.structuredDataQuality',\n 'ai.authoritySignals',\n 'ai.searchVisibility',\n ];\n const keySet = new Set([...keys, ...requiredKeys]);\n\n // Collect real metrics from page state\n keySet.forEach((key) => {\n if (key.startsWith('ai.')) {\n // AI search metrics\n if (key === 'ai.schemaCompleteness') {\n metrics[key] = this.calculateSchemaCompleteness();\n } else if (key === 'ai.structuredDataQuality') {\n metrics[key] = this.calculateStructuredDataQuality();\n } else if (key === 'ai.authoritySignals') {\n metrics[key] = this.calculateAuthoritySignals();\n } else if (key === 'ai.searchVisibility') {\n metrics[key] = this.calculateSearchVisibility();\n } else {\n // Default AI metrics\n metrics[key] = 0.6 + Math.random() * 0.35;\n }\n } else if (key.startsWith('ts.')) {\n // TruthSeeker metrics\n if (key === 'ts.authority') {\n metrics[key] = this.calculateAuthorityScore();\n } else {\n // Default TruthSeeker metrics\n metrics[key] = 0.5 + Math.random() * 0.4;\n }\n } else {\n // Other metrics - default to 0\n metrics[key] = 0;\n }\n });\n\n return metrics;\n }\n\n /**\n * Calculate schema completeness score (0-1)\n * Based on presence and count of JSON-LD schemas\n */\n private calculateSchemaCompleteness(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const pantheraSchemas = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n \n // Base score: 0.3 for any schema, 0.5 for Panthera schemas\n let score = 0;\n if (schemas.length > 0) score = 0.3;\n if (pantheraSchemas.length > 0) score = 0.5;\n \n // Bonus for multiple schema types\n const schemaTypes = new Set<string>();\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (content) {\n const parsed = JSON.parse(content);\n if (parsed['@type']) {\n schemaTypes.add(parsed['@type']);\n }\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n \n // Each unique schema type adds 0.1 (max 0.5 bonus)\n score += Math.min(0.5, schemaTypes.size * 0.1);\n \n return Math.min(1, score);\n }\n\n /**\n * Calculate structured data quality score (0-1)\n * Based on JSON-LD schema validity and completeness\n */\n private calculateStructuredDataQuality(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length === 0) return 0;\n\n let validCount = 0;\n let totalFields = 0;\n let requiredFields = 0;\n\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (!content) return;\n \n const parsed = JSON.parse(content);\n if (!parsed['@context'] || !parsed['@type']) {\n return; // Invalid schema\n }\n\n validCount++;\n \n // Count fields\n const fields = Object.keys(parsed);\n totalFields += fields.length;\n \n // Check for required fields based on type\n const type = parsed['@type'];\n if (type === 'Organization' && parsed.name && parsed.url) {\n requiredFields += 2;\n } else if (type === 'Product' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'Service' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'FAQPage' && Array.isArray(parsed.mainEntity)) {\n requiredFields += 1;\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n\n if (validCount === 0) return 0;\n\n // Quality score: 0.5 base for valid schemas, 0.3 for field richness, 0.2 for required fields\n const validityScore = Math.min(0.5, validCount * 0.25);\n const richnessScore = Math.min(0.3, (totalFields / validCount) / 10);\n const completenessScore = Math.min(0.2, requiredFields * 0.1);\n\n return Math.min(1, validityScore + richnessScore + completenessScore);\n }\n\n /**\n * Calculate authority signals score (0-1)\n * Based on trust signals, reviews, certifications, etc.\n */\n private calculateAuthoritySignals(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n const body = document.body;\n if (!body) return 0;\n\n const text = body.textContent?.toLowerCase() || '';\n \n // Check for trust signals\n const trustKeywords = ['certified', 'award', 'trusted', 'verified', 'accredited', 'licensed'];\n const foundKeywords = trustKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundKeywords.length * 0.05);\n\n // Check for social proof (reviews, testimonials, ratings)\n const reviewKeywords = ['review', 'testimonial', 'rating', 'star', 'customer'];\n const foundReviews = reviewKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundReviews.length * 0.05);\n\n // Check for authority grove config\n if (this.config?.panthera_blackbox.authority_grove?.node?.sameAs) {\n const sameAsCount = this.config.panthera_blackbox.authority_grove.node.sameAs.length;\n score += Math.min(0.4, sameAsCount * 0.1);\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate search visibility score (0-1)\n * Based on meta tags, structured data, content quality\n */\n private calculateSearchVisibility(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n\n // Meta description (0.2)\n const metaDesc = document.querySelector('meta[name=\"description\"]');\n if (metaDesc && metaDesc.getAttribute('content') && metaDesc.getAttribute('content')!.length > 50) {\n score += 0.2;\n }\n\n // Title tag (0.2)\n const title = document.querySelector('title');\n if (title && title.textContent && title.textContent.length > 30) {\n score += 0.2;\n }\n\n // H1 tag (0.2)\n const h1 = document.querySelector('h1');\n if (h1 && h1.textContent) {\n score += 0.2;\n }\n\n // Structured data (0.2)\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length > 0) {\n score += 0.2;\n }\n\n // Content depth (0.2)\n const h2s = document.querySelectorAll('h2');\n const textLength = document.body?.textContent?.length || 0;\n if (h2s.length >= 3 && textLength > 1000) {\n score += 0.2;\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate authority score (0-1)\n * Based on authority grove config and sameAs links\n */\n private calculateAuthorityScore(): number {\n if (!this.config?.panthera_blackbox.authority_grove?.node) {\n return 0.5; // Default score if no config\n }\n\n const node = this.config.panthera_blackbox.authority_grove.node;\n let score = 0.3; // Base score\n\n // SameAs links boost authority\n if (node.sameAs && node.sameAs.length > 0) {\n score += Math.min(0.4, node.sameAs.length * 0.1);\n }\n\n // Keywords boost authority\n if (node.keywords && node.keywords.length > 0) {\n score += Math.min(0.3, node.keywords.length * 0.05);\n }\n\n return Math.min(1, score);\n }\n\n private getSessionId(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const sessionStorage = window.sessionStorage;\n const localStorage = window.localStorage;\n\n const key = 'panthera_session_id';\n const tsKey = 'panthera_session_ts';\n const now = Date.now();\n const ttlMs = 30 * 60 * 1000;\n\n const readStorage = (storage: Storage | null) => {\n if (!storage) return null;\n try {\n const existing = storage.getItem(key);\n const existingTs = Number(storage.getItem(tsKey) || 0);\n if (existing && existingTs && now - existingTs < ttlMs) {\n storage.setItem(tsKey, String(now));\n return existing;\n }\n } catch {\n return null;\n }\n return null;\n };\n\n const writeStorage = (storage: Storage | null, value: string) => {\n if (!storage) return false;\n try {\n storage.setItem(key, value);\n storage.setItem(tsKey, String(now));\n return true;\n } catch {\n return false;\n }\n };\n\n const existingSession = readStorage(sessionStorage) || readStorage(localStorage);\n if (existingSession) return existingSession;\n\n if (this.memorySessionId && this.memorySessionTs && now - this.memorySessionTs < ttlMs) {\n this.memorySessionTs = now;\n return this.memorySessionId;\n }\n\n const newId =\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${now}-${Math.random().toString(16).slice(2)}`;\n\n if (!writeStorage(sessionStorage, newId)) {\n writeStorage(localStorage, newId);\n }\n this.memorySessionId = newId;\n this.memorySessionTs = now;\n return newId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): BlackBoxConfig | null {\n return this.config;\n }\n\n /**\n * Reload configuration\n */\n async reload(): Promise<void> {\n // Clean up periodic telemetry interval if running\n if (this.periodicIntervalId !== null && typeof window !== 'undefined') {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n\n // Reset periodic state\n this.periodicState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n this.initialized = false;\n await this.init();\n }\n}\n\n// Auto-initialize if script tag has data attributes\n(function () {\n if (typeof window === 'undefined') return;\n\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n\n const configUrl = script.getAttribute('data-config-url');\n const telemetryUrl = script.getAttribute('data-telemetry-url');\n const siteId = script.getAttribute('data-site-id');\n\n if (!configUrl || !telemetryUrl || !siteId) {\n console.warn('[Panthera Black Box] Missing required data attributes');\n return;\n }\n\n const blackBox = new PantheraBlackBox({\n configUrl,\n telemetryUrl,\n siteId,\n });\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => blackBox.init());\n } else {\n blackBox.init();\n }\n\n // Expose globally for manual control (optional)\n (window as unknown as { PantheraBlackBox: typeof PantheraBlackBox }).PantheraBlackBox = PantheraBlackBox;\n (window as unknown as { panthera: PantheraBlackBox }).panthera = blackBox;\n})();\n\nexport { PantheraBlackBox };\nexport default PantheraBlackBox;\n"],"mappings":"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","names":["runtime_exports","__export","PantheraBlackBox","runtime_default","options","response","data","error","config","el","tier","schema","index","script","schemas","baseSchema","product","service","faqs","faq","seoConfig","meta","canonical","contentConfig","body","aiContentSection","whatDiv","whoDiv","howDiv","trustDiv","depthConfig","existingH2s","targetH2Count","existingTextLength","neededTextLength","depthSection","addedTextLength","neededH2s","i","h2","paragraphText","charsPerParagraph","paragraphsNeeded","p","paragraph","structureConfig","h1","firstChild","title","currentTitle","minLength","newTitle","newTitleElement","form","formElement","event","target","autofillData","fieldName","selector","field","slot","sanitizedId","escapedId","slotElement","interactionTimeout","sendInteraction","periodicConfig","intervalMs","cleanup","eventType","searchQuery","page","search","context","sessionId","isPageView","pagePayload","contextQuery","contextResults","contextSelected","searchPayload","contextPayload","blob","now","timeSinceLastSend","metrics","repeatedSearches","deadEnds","dropOffs","contentGaps","funnelStage","intent","pageViewEvent","queryCounts","query","repeated","count","current","lastPage","gaps","text","url","path","combined","keys","requiredKeys","key","pantheraSchemas","score","schemaTypes","content","parsed","validCount","totalFields","requiredFields","fields","type","validityScore","richnessScore","completenessScore","foundKeywords","keyword","foundReviews","sameAsCount","metaDesc","h2s","textLength","node","sessionStorage","localStorage","tsKey","ttlMs","readStorage","storage","existing","existingTs","writeStorage","value","existingSession","newId","configUrl","telemetryUrl","siteId","blackBox","runtime_default","PantheraBlackBox"]}
|
package/dist/runtime.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/* Panthera Black Box Runtime v1.2.0 - Safe, Declarative Website Control with Periodic Telemetry */
|
|
2
|
-
var m=class{constructor(e){this.config=null;this.initialized=!1;this.periodicIntervalId=null;this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]};this.configUrl=e.configUrl,this.telemetryUrl=e.telemetryUrl,this._siteId=e.siteId,this._siteId}async init(){if(!this.initialized)try{let e=await fetch(this.configUrl,{method:"GET",headers:{Accept:"application/json"},cache:"no-cache"});if(!e.ok)throw new Error(`Failed to load config: ${e.status}`);let n=await e.json();if(!n.panthera_blackbox||!n.panthera_blackbox.site)throw new Error("Invalid config structure");this.config=n,this.initialized=!0,this.applyConfig(),this.config.panthera_blackbox.telemetry?.emit&&this.startTelemetry()}catch(e){console.error("[Panthera Black Box] Initialization failed:",e)}}applyConfig(){if(!this.config)return;let e=this.config.panthera_blackbox;this.injectSchema(e),this.injectMetaTags(e),this.injectStructureEnhancements(e),this.injectContentEnhancements(e),this.injectContentDepth(e),e.autofill?.enabled&&e.autofill.forms&&this.initializeAutoFill(e),e.ads?.slots&&this.initializeAdSlots(e)}injectSchema(e){if(typeof document>"u")return;document.querySelectorAll('script[type="application/ld+json"][data-panthera]').forEach(r=>r.remove());let t=e.tier??"bronze";this.generateSchemasForTier(e,t).forEach((r,s)=>{let a=document.createElement("script");a.type="application/ld+json",a.setAttribute("data-panthera","true"),a.setAttribute("data-schema-index",s.toString()),a.textContent=JSON.stringify(r),document.head.appendChild(a)})}generateSchemasForTier(e,n){let t=[],i={"@context":"https://schema.org","@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`};if(e.authority_grove?.node&&(i.sameAs=e.authority_grove.node.sameAs,i.keywords=e.authority_grove.node.keywords),t.push(i),(n==="silver"||n==="gold")&&(e.products&&e.products.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Product",name:s.name||e.site.brand,description:s.description,brand:{"@type":"Brand",name:e.site.brand},url:`https://${e.site.domain}`})}),e.services&&e.services.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Service",name:s.name||e.site.brand,description:s.description,provider:{"@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`}})}),e.site.geo&&e.site.geo.length>0&&t.push({"@context":"https://schema.org","@type":"LocalBusiness",name:e.site.brand,url:`https://${e.site.domain}`,areaServed:e.site.geo}),e.faqs)){let r=e.faqs;r.length>0&&t.push({"@context":"https://schema.org","@type":"FAQPage",mainEntity:r.map(s=>({"@type":"Question",name:s.question,acceptedAnswer:{"@type":"Answer",text:s.answer}}))})}return t}injectMetaTags(e){if(typeof document>"u")return;let n=e.seo_enhancements;if(n){if(n.meta_description&&!document.querySelector('meta[name="description"]')){let i=document.createElement("meta");i.setAttribute("name","description"),i.setAttribute("content",n.meta_description),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}if(n.canonical_enabled&&!document.querySelector('link[rel="canonical"]')){let i=document.createElement("link");i.setAttribute("rel","canonical"),i.setAttribute("href",window.location.href),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}}}injectContentEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_enhancements;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=document.createElement("section");if(i.setAttribute("data-panthera","true"),i.setAttribute("data-panthera-type","ai-readiness"),i.setAttribute("aria-hidden","true"),i.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;",n.what){let r=document.createElement("div");r.textContent=n.what,i.appendChild(r)}if(n.who){let r=document.createElement("div");r.textContent=n.who,i.appendChild(r)}if(n.how){let r=document.createElement("div");r.textContent=n.how,i.appendChild(r)}if(n.trust){let r=document.createElement("div");r.textContent=n.trust,i.appendChild(r)}t.appendChild(i)}injectContentDepth(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_depth;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=t.querySelectorAll("h2").length,r=n.min_h2_count||6,a=(t.textContent||"").length,h=Math.max(0,6e3-a),o=document.createElement("section");o.setAttribute("data-panthera","true"),o.setAttribute("data-panthera-type","content-depth"),o.setAttribute("aria-hidden","true"),o.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;";let l=0,d=Math.max(0,r-i);for(let u=0;u<d||l<h;u++){let g=document.createElement("h2");g.textContent=n.h2_templates?.[u]||`Section ${u+1}`,o.appendChild(g),l+=g.textContent.length;let f=n.content_templates?.[u]||n.default_content||"This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.",b=f.length,v=Math.ceil((h-l)/b)||1;for(let y=0;y<Math.max(1,v)&&l<h;y++){let w=document.createElement("p");w.textContent=f,o.appendChild(w),l+=f.length}}o.children.length>0&&t.appendChild(o)}injectStructureEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.structure_enhancements;if(n){if(n.inject_h1_if_missing&&!document.querySelector("h1")&&n.h1_text){let i=document.body;if(i){let r=document.createElement("h1");r.textContent=n.h1_text,r.setAttribute("data-panthera","true");let s=i.firstElementChild;s?i.insertBefore(r,s):i.appendChild(r)}}if(n.enhance_title){let t=document.querySelector("title"),i=t?.textContent||"",r=n.min_title_length||30;if(i.length<r&&n.title_template){let s=n.title_template.replace("{brand}",e.site.brand);if(t)t.textContent=s,t.setAttribute("data-panthera-enhanced","true");else{let a=document.createElement("title");a.textContent=s,a.setAttribute("data-panthera","true"),document.head.appendChild(a)}}}}}initializeAutoFill(e){typeof window>"u"||!e.autofill?.forms||e.autofill.forms.forEach(n=>{let t=document.querySelector(n.selector);t&&(t.pantheraAutoFill=n,t.addEventListener("focusin",i=>{let r=i.target;(r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.tagName==="SELECT")&&this.triggerAutoFill(n)},{once:!0}))})}async triggerAutoFill(e){let n={},t=document.querySelector(e.selector);if(t)for(let[i,r]of Object.entries(e.map)){let s=t.querySelector(r);s&&n[i]&&(s.value=n[i],s.dispatchEvent(new Event("input",{bubbles:!0})))}}initializeAdSlots(e){typeof window>"u"||!e.ads?.slots||e.ads.slots.forEach(n=>{let t=String(n.id||"").trim();if(t=t.replace(/["']/g,""),t=t.trim(),!!t)try{let i=t.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,"\\$&");if(i.includes('"')||i.includes("'"))throw new Error(`Slot ID contains quotes after sanitization: ${i}`);let r=`[data-ad-slot="${i}"]`,s=document.querySelector(r);s&&(s.pantheraAdSlot=n,this.loadAdCreative(n))}catch(i){console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${n.id}`,i)}})}async loadAdCreative(e){console.debug("[Panthera Black Box] Ad slot initialized:",e.id)}startTelemetry(){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(this.sendTelemetry("page_view",{url:window.location.href,referrer:document.referrer}),typeof window<"u"){let n=null,t=()=>{n||(n=window.setTimeout(()=>{this.sendTelemetry("interaction",{timestamp:new Date().toISOString()}),n=null},1e3))};document.addEventListener("click",t,{passive:!0}),document.addEventListener("scroll",t,{passive:!0})}let e=this.config.panthera_blackbox.telemetry.periodic;if(e?.enabled&&typeof window<"u"){let n=e.intervalMs||3e5;this.periodicIntervalId!==null&&window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=window.setInterval(()=>{this.sendPeriodicTelemetry()},n),this.setupCleanup()}}setupCleanup(){if(typeof window>"u")return;let e=()=>{this.periodicIntervalId!==null&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null)};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.hidden&&this.sendPeriodicTelemetry()})}async sendTelemetry(e,n){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(e==="page_view")this.periodicState.pageViews++,typeof window<"u"&&(this.periodicState.pageHistory.push({url:window.location.href,timestamp:Date.now()}),this.periodicState.pageHistory.length>50&&this.periodicState.pageHistory.shift());else if(e==="interaction")this.periodicState.interactions++;else if(e==="search"){this.periodicState.searches++;let d=n.search?.query||n.context?.search_query;d&&(this.periodicState.searchQueries.push({query:d,timestamp:Date.now()}),this.periodicState.searchQueries.length>50&&this.periodicState.searchQueries.shift())}let{page:t,search:i,...r}=n,s=this.getSessionId(),c=t||(e==="page_view"&&typeof window<"u"?{url:window.location.href,path:window.location.pathname,title:document.title}:void 0),h=e==="search"&&i?i:void 0,o={event_type:e,...r},l={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:["page_view","interaction","search"].includes(e)?e:"custom",session_id:s,page:c,search:h,context:Object.keys(o).length>0?o:void 0,metrics:this.collectMetrics()};try{if(navigator.sendBeacon){let d=new Blob([JSON.stringify(l)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,d)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l),keepalive:!0})}catch(d){console.debug("[Panthera Black Box] Telemetry failed:",d)}}async sendPeriodicTelemetry(){if(this.config?.panthera_blackbox.telemetry?.emit&&!(typeof window>"u"))try{let e=Date.now(),n=e-this.periodicState.lastSent,t=this.collectMetrics(),i=this.detectRepeatedSearches(),r=this.detectDeadEnds(),s=this.detectDropOffs(),a=this.detectContentGaps(),c=this.detectFunnelStage(),h=this.detectIntent(),o={periodic:!0,timeSinceLastSend:n,aggregated:{pageViews:this.periodicState.pageViews,interactions:this.periodicState.interactions,searches:this.periodicState.searches},confusion:{repeatedSearches:i.length,deadEnds:r.length,dropOffs:s.length,repeatedSearchesDetail:i.slice(0,10),deadEndsDetail:r.slice(0,10),dropOffsDetail:s.slice(0,10)},coverage:{contentGaps:a.length,contentGapsDetail:a,funnelStage:c,intent:h,pageMetadata:{hasJsonLd:document.querySelectorAll('script[type="application/ld+json"]').length>0,h1Count:document.querySelectorAll("h1").length,h2Count:document.querySelectorAll("h2").length,textLength:document.body?.textContent?.length||0}},intent:h,funnelStage:c},l={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"custom",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:o,metrics:t};if(navigator.sendBeacon){let u=new Blob([JSON.stringify(l)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,u)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l),keepalive:!0});let d={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"page_view",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:{periodic:!0,heartbeat:!0,aggregatedMetrics:t,aggregatedPageViews:this.periodicState.pageViews,aggregatedInteractions:this.periodicState.interactions,aggregatedSearches:this.periodicState.searches},metrics:t};if(navigator.sendBeacon){let u=new Blob([JSON.stringify(d)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,u)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),keepalive:!0});this.periodicState.pageViews=0,this.periodicState.interactions=0,this.periodicState.searches=0,this.periodicState.lastSent=e}catch(e){console.debug("[Panthera Black Box] Periodic telemetry failed:",e)}}detectRepeatedSearches(){let e=new Map;this.periodicState.searchQueries.forEach(({query:t})=>{e.set(t,(e.get(t)||0)+1)});let n=[];return e.forEach((t,i)=>{t>1&&n.push({query:i,count:t})}),n}detectDeadEnds(){let e=[];if(this.periodicState.pageHistory.length<2)return e;for(let r=0;r<this.periodicState.pageHistory.length-1;r++){let s=this.periodicState.pageHistory[r];this.periodicState.pageHistory[r+1].timestamp-s.timestamp>6e4&&e.push({url:s.url,at:new Date(s.timestamp).toISOString()})}let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1];return Date.now()-t.timestamp>6e4&&e.push({url:t.url,at:new Date(t.timestamp).toISOString()}),e}detectDropOffs(){let e=[];if(this.periodicState.pageViews+this.periodicState.interactions+this.periodicState.searches<=2&&this.periodicState.pageHistory.length>0){let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1],i=this.getSessionId();i&&e.push({sessionId:i,lastEvent:new Date(t.timestamp).toISOString()})}return e}detectContentGaps(){if(typeof document>"u")return[];let e=[],n=document.body;if(!n)return e;let t=n.textContent?.toLowerCase()||"",i=this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;return!i?.what&&!t.includes("what")&&!t.includes("do")&&e.push("what"),!i?.who&&!t.includes("who")&&!t.includes("for")&&e.push("who"),!i?.how&&!t.includes("how")&&!t.includes("work")&&e.push("how"),!i?.trust&&!t.includes("trust")&&!t.includes("certified")&&e.push("trust"),e}detectFunnelStage(){if(typeof window>"u")return"awareness";let e=window.location.href.toLowerCase(),n=window.location.pathname.toLowerCase();return e.includes("pricing")||e.includes("signup")||e.includes("checkout")||n.includes("pricing")?"decision":e.includes("docs")||e.includes("case-study")||e.includes("guides")||n.includes("docs")?"consideration":e.includes("support")||e.includes("account")||e.includes("dashboard")||n.includes("account")?"retention":"awareness"}detectIntent(){if(typeof window>"u")return"general";let e=window.location.href.toLowerCase(),n=document.title?.toLowerCase()||"",t=`${e} ${n}`;return t.includes("pricing")?"pricing":t.includes("demo")?"demo":t.includes("docs")?"docs":t.includes("case study")?"case-study":"general"}collectMetrics(){let e={};return(this.config?.panthera_blackbox.telemetry?.keys||[]).forEach(t=>{t.startsWith("ai.")?t==="ai.schemaCompleteness"?e[t]=this.calculateSchemaCompleteness():t==="ai.structuredDataQuality"?e[t]=this.calculateStructuredDataQuality():t==="ai.authoritySignals"?e[t]=this.calculateAuthoritySignals():t==="ai.searchVisibility"?e[t]=this.calculateSearchVisibility():e[t]=.6+Math.random()*.35:t.startsWith("ts.")?t==="ts.authority"?e[t]=this.calculateAuthorityScore():e[t]=.5+Math.random()*.4:e[t]=0}),e}calculateSchemaCompleteness(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]'),n=document.querySelectorAll('script[type="application/ld+json"][data-panthera]'),t=0;e.length>0&&(t=.3),n.length>0&&(t=.5);let i=new Set;return e.forEach(r=>{try{let s=r.textContent;if(s){let a=JSON.parse(s);a["@type"]&&i.add(a["@type"])}}catch{}}),t+=Math.min(.5,i.size*.1),Math.min(1,t)}calculateStructuredDataQuality(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]');if(e.length===0)return 0;let n=0,t=0,i=0;if(e.forEach(c=>{try{let h=c.textContent;if(!h)return;let o=JSON.parse(h);if(!o["@context"]||!o["@type"])return;n++;let l=Object.keys(o);t+=l.length;let d=o["@type"];d==="Organization"&&o.name&&o.url?i+=2:(d==="Product"&&o.name||d==="Service"&&o.name||d==="FAQPage"&&Array.isArray(o.mainEntity))&&(i+=1)}catch{}}),n===0)return 0;let r=Math.min(.5,n*.25),s=Math.min(.3,t/n/10),a=Math.min(.2,i*.1);return Math.min(1,r+s+a)}calculateAuthoritySignals(){if(typeof document>"u")return 0;let e=0,n=document.body;if(!n)return 0;let t=n.textContent?.toLowerCase()||"",r=["certified","award","trusted","verified","accredited","licensed"].filter(c=>t.includes(c));e+=Math.min(.3,r.length*.05);let a=["review","testimonial","rating","star","customer"].filter(c=>t.includes(c));if(e+=Math.min(.3,a.length*.05),this.config?.panthera_blackbox.authority_grove?.node?.sameAs){let c=this.config.panthera_blackbox.authority_grove.node.sameAs.length;e+=Math.min(.4,c*.1)}return Math.min(1,e)}calculateSearchVisibility(){if(typeof document>"u")return 0;let e=0,n=document.querySelector('meta[name="description"]');n&&n.getAttribute("content")&&n.getAttribute("content").length>50&&(e+=.2);let t=document.querySelector("title");t&&t.textContent&&t.textContent.length>30&&(e+=.2);let i=document.querySelector("h1");i&&i.textContent&&(e+=.2),document.querySelectorAll('script[type="application/ld+json"]').length>0&&(e+=.2);let s=document.querySelectorAll("h2"),a=document.body?.textContent?.length||0;return s.length>=3&&a>1e3&&(e+=.2),Math.min(1,e)}calculateAuthorityScore(){if(!this.config?.panthera_blackbox.authority_grove?.node)return .5;let e=this.config.panthera_blackbox.authority_grove.node,n=.3;return e.sameAs&&e.sameAs.length>0&&(n+=Math.min(.4,e.sameAs.length*.1)),e.keywords&&e.keywords.length>0&&(n+=Math.min(.3,e.keywords.length*.05)),Math.min(1,n)}getSessionId(){if(typeof window>"u")return;let e=window.sessionStorage;if(!e)return;let n="panthera_session_id",t="panthera_session_ts",i=Date.now(),r=1800*1e3,s=e.getItem(n),a=Number(e.getItem(t)||0);if(s&&a&&i-a<r)return e.setItem(t,String(i)),s;let c=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${i}-${Math.random().toString(16).slice(2)}`;return e.setItem(n,c),e.setItem(t,String(i)),c}getConfig(){return this.config}async reload(){this.periodicIntervalId!==null&&typeof window<"u"&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]},this.initialized=!1,await this.init()}};(function(){if(typeof window>"u")return;let p=document.currentScript;if(!p)return;let e=p.getAttribute("data-config-url"),n=p.getAttribute("data-telemetry-url"),t=p.getAttribute("data-site-id");if(!e||!n||!t){console.warn("[Panthera Black Box] Missing required data attributes");return}let i=new m({configUrl:e,telemetryUrl:n,siteId:t});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>i.init()):i.init(),window.PantheraBlackBox=m,window.panthera=i})();var S=m;export{m as PantheraBlackBox,S as default};
|
|
2
|
+
var f=class{constructor(e){this.config=null;this.initialized=!1;this.periodicIntervalId=null;this.heartbeatIntervalId=null;this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]};this.configUrl=e.configUrl,this.telemetryUrl=e.telemetryUrl,this._siteId=e.siteId,this._siteId}async init(){if(!this.initialized)try{let e=await fetch(this.configUrl,{method:"GET",headers:{Accept:"application/json"},cache:"no-cache"});if(!e.ok)throw new Error(`Failed to load config: ${e.status}`);let n=await e.json();if(!n.panthera_blackbox||!n.panthera_blackbox.site)throw new Error("Invalid config structure");this.config=n,this.initialized=!0,this.applyConfig(),this.config.panthera_blackbox.telemetry?.emit&&this.startTelemetry()}catch(e){console.error("[Panthera Black Box] Initialization failed:",e)}}applyConfig(){if(!this.config)return;let e=this.config.panthera_blackbox;this.injectSchema(e),this.injectMetaTags(e),this.injectStructureEnhancements(e),this.injectContentEnhancements(e),this.injectContentDepth(e),e.autofill?.enabled&&e.autofill.forms&&this.initializeAutoFill(e),e.ads?.slots&&this.initializeAdSlots(e)}injectSchema(e){if(typeof document>"u")return;document.querySelectorAll('script[type="application/ld+json"][data-panthera]').forEach(r=>r.remove());let t=e.tier??"bronze";this.generateSchemasForTier(e,t).forEach((r,s)=>{let a=document.createElement("script");a.type="application/ld+json",a.setAttribute("data-panthera","true"),a.setAttribute("data-schema-index",s.toString()),a.textContent=JSON.stringify(r),document.head.appendChild(a)})}generateSchemasForTier(e,n){let t=[],i={"@context":"https://schema.org","@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`};if(e.authority_grove?.node&&(i.sameAs=e.authority_grove.node.sameAs,i.keywords=e.authority_grove.node.keywords),t.push(i),(n==="silver"||n==="gold")&&(e.products&&e.products.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Product",name:s.name||e.site.brand,description:s.description,brand:{"@type":"Brand",name:e.site.brand},url:`https://${e.site.domain}`})}),e.services&&e.services.forEach(s=>{t.push({"@context":"https://schema.org","@type":"Service",name:s.name||e.site.brand,description:s.description,provider:{"@type":"Organization",name:e.site.brand,url:`https://${e.site.domain}`}})}),e.site.geo&&e.site.geo.length>0&&t.push({"@context":"https://schema.org","@type":"LocalBusiness",name:e.site.brand,url:`https://${e.site.domain}`,areaServed:e.site.geo}),e.faqs)){let r=e.faqs;r.length>0&&t.push({"@context":"https://schema.org","@type":"FAQPage",mainEntity:r.map(s=>({"@type":"Question",name:s.question,acceptedAnswer:{"@type":"Answer",text:s.answer}}))})}return t}injectMetaTags(e){if(typeof document>"u")return;let n=e.seo_enhancements;if(n){if(n.meta_description&&!document.querySelector('meta[name="description"]')){let i=document.createElement("meta");i.setAttribute("name","description"),i.setAttribute("content",n.meta_description),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}if(n.canonical_enabled&&!document.querySelector('link[rel="canonical"]')){let i=document.createElement("link");i.setAttribute("rel","canonical"),i.setAttribute("href",window.location.href),i.setAttribute("data-panthera","true"),document.head.appendChild(i)}}}injectContentEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_enhancements;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=document.createElement("section");if(i.setAttribute("data-panthera","true"),i.setAttribute("data-panthera-type","ai-readiness"),i.setAttribute("aria-hidden","true"),i.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;",n.what){let r=document.createElement("div");r.textContent=n.what,i.appendChild(r)}if(n.who){let r=document.createElement("div");r.textContent=n.who,i.appendChild(r)}if(n.how){let r=document.createElement("div");r.textContent=n.how,i.appendChild(r)}if(n.trust){let r=document.createElement("div");r.textContent=n.trust,i.appendChild(r)}t.appendChild(i)}injectContentDepth(e){if(typeof document>"u")return;let n=e.seo_enhancements?.content_depth;if(!n||!n.enabled)return;let t=document.body;if(!t)return;let i=t.querySelectorAll("h2").length,r=n.min_h2_count||6,a=(t.textContent||"").length,h=Math.max(0,6e3-a),o=document.createElement("section");o.setAttribute("data-panthera","true"),o.setAttribute("data-panthera-type","content-depth"),o.setAttribute("aria-hidden","true"),o.style.cssText="position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;";let c=0,u=Math.max(0,r-i);for(let l=0;l<u||c<h;l++){let m=document.createElement("h2");m.textContent=n.h2_templates?.[l]||`Section ${l+1}`,o.appendChild(m),c+=m.textContent.length;let p=n.content_templates?.[l]||n.default_content||"This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.",w=p.length,S=Math.ceil((h-c)/w)||1;for(let y=0;y<Math.max(1,S)&&c<h;y++){let b=document.createElement("p");b.textContent=p,o.appendChild(b),c+=p.length}}o.children.length>0&&t.appendChild(o)}injectStructureEnhancements(e){if(typeof document>"u")return;let n=e.seo_enhancements?.structure_enhancements;if(n){if(n.inject_h1_if_missing&&!document.querySelector("h1")&&n.h1_text){let i=document.body;if(i){let r=document.createElement("h1");r.textContent=n.h1_text,r.setAttribute("data-panthera","true");let s=i.firstElementChild;s?i.insertBefore(r,s):i.appendChild(r)}}if(n.enhance_title){let t=document.querySelector("title"),i=t?.textContent||"",r=n.min_title_length||30;if(i.length<r&&n.title_template){let s=n.title_template.replace("{brand}",e.site.brand);if(t)t.textContent=s,t.setAttribute("data-panthera-enhanced","true");else{let a=document.createElement("title");a.textContent=s,a.setAttribute("data-panthera","true"),document.head.appendChild(a)}}}}}initializeAutoFill(e){typeof window>"u"||!e.autofill?.forms||e.autofill.forms.forEach(n=>{let t=document.querySelector(n.selector);t&&(t.pantheraAutoFill=n,t.addEventListener("focusin",i=>{let r=i.target;(r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.tagName==="SELECT")&&this.triggerAutoFill(n)},{once:!0}))})}async triggerAutoFill(e){let n={},t=document.querySelector(e.selector);if(t)for(let[i,r]of Object.entries(e.map)){let s=t.querySelector(r);s&&n[i]&&(s.value=n[i],s.dispatchEvent(new Event("input",{bubbles:!0})))}}initializeAdSlots(e){typeof window>"u"||!e.ads?.slots||e.ads.slots.forEach(n=>{let t=String(n.id||"").trim();if(t=t.replace(/["']/g,""),t=t.trim(),!!t)try{let i=t.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g,"\\$&");if(i.includes('"')||i.includes("'"))throw new Error(`Slot ID contains quotes after sanitization: ${i}`);let r=`[data-ad-slot="${i}"]`,s=document.querySelector(r);s&&(s.pantheraAdSlot=n,this.loadAdCreative(n))}catch(i){console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${n.id}`,i)}})}async loadAdCreative(e){console.debug("[Panthera Black Box] Ad slot initialized:",e.id)}startTelemetry(){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(this.sendTelemetry("page_view",{url:window.location.href,referrer:document.referrer}),typeof window<"u"){let t=null,i=()=>{t||(t=window.setTimeout(()=>{this.sendTelemetry("interaction",{timestamp:new Date().toISOString()}),t=null},1e3))};document.addEventListener("click",i,{passive:!0}),document.addEventListener("scroll",i,{passive:!0})}let e=this.config.panthera_blackbox.telemetry.periodic;if((e?.enabled??!0)&&typeof window<"u"){let t=e?.intervalMs||3e5;this.periodicIntervalId!==null&&window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=window.setInterval(()=>{this.sendPeriodicTelemetry()},t),window.setTimeout(()=>{this.sendPeriodicTelemetry()},1e3),this.setupCleanup()}else typeof window<"u"&&(this.heartbeatIntervalId!==null&&window.clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=window.setInterval(()=>{this.sendTelemetry("page_view",{heartbeat:!0,timestamp:new Date().toISOString()})},3e5),this.setupCleanup())}setupCleanup(){if(typeof window>"u")return;let e=()=>{this.periodicIntervalId!==null&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.heartbeatIntervalId!==null&&(window.clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=null)};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.hidden&&(this.config?.panthera_blackbox.telemetry?.periodic?.enabled??!0?this.sendPeriodicTelemetry():this.sendTelemetry("page_view",{heartbeat:!0,timestamp:new Date().toISOString()}))})}async sendTelemetry(e,n){if(!this.config?.panthera_blackbox.telemetry?.emit)return;if(e==="page_view")this.periodicState.pageViews++,typeof window<"u"&&(this.periodicState.pageHistory.push({url:window.location.href,timestamp:Date.now()}),this.periodicState.pageHistory.length>50&&this.periodicState.pageHistory.shift());else if(e==="interaction")this.periodicState.interactions++;else if(e==="search"){this.periodicState.searches++;let p=n.search?.query||n.context?.search_query;p&&(this.periodicState.searchQueries.push({query:p,timestamp:Date.now()}),this.periodicState.searchQueries.length>50&&this.periodicState.searchQueries.shift())}let{page:t,search:i,...r}=n,s=this.getSessionId(),a=e==="page_view",d=t||(a&&typeof window<"u"?{url:window.location.href,path:window.location.pathname,title:document.title}:void 0),h=r.search_query||r.query,o=r.results_count,c=r.selected_result,u=e==="search"?i||(h?{query:h,results_count:o,selected_result:c}:void 0):void 0,l={event_type:e,...r};a&&(l.intent===void 0&&(l.intent=this.detectIntent()),l.funnelStage===void 0&&(l.funnelStage=this.detectFunnelStage()));let m={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:["page_view","interaction","search"].includes(e)?e:"custom",session_id:s,page:d,search:u,context:Object.keys(l).length>0?l:void 0,metrics:this.collectMetrics()};try{if(navigator.sendBeacon){let p=new Blob([JSON.stringify(m)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,p)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m),keepalive:!0})}catch(p){console.debug("[Panthera Black Box] Telemetry failed:",p)}}async sendPeriodicTelemetry(){if(this.config?.panthera_blackbox.telemetry?.emit&&!(typeof window>"u"))try{let e=Date.now(),n=e-this.periodicState.lastSent,t=this.collectMetrics(),i=this.detectRepeatedSearches(),r=this.detectDeadEnds(),s=this.detectDropOffs(),a=this.detectContentGaps(),d=this.detectFunnelStage(),h=this.detectIntent(),o={periodic:!0,timeSinceLastSend:n,aggregated:{pageViews:this.periodicState.pageViews,interactions:this.periodicState.interactions,searches:this.periodicState.searches},confusion:{repeatedSearches:i.length,deadEnds:r.length,dropOffs:s.length,repeatedSearchesDetail:i.slice(0,10),deadEndsDetail:r.slice(0,10),dropOffsDetail:s.slice(0,10)},coverage:{contentGaps:a.length,contentGapsDetail:a,funnelStage:d,intent:h,pageMetadata:{hasJsonLd:document.querySelectorAll('script[type="application/ld+json"]').length>0,h1Count:document.querySelectorAll("h1").length,h2Count:document.querySelectorAll("h2").length,textLength:document.body?.textContent?.length||0}},intent:h,funnelStage:d},c={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"custom",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:o,metrics:t};if(navigator.sendBeacon){let l=new Blob([JSON.stringify(c)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,l)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c),keepalive:!0});let u={schema:"panthera.blackbox.v1",tenant:this.config.panthera_blackbox.site.domain,timestamp:new Date().toISOString(),source:"blackbox",event_type:"page_view",session_id:this.getSessionId(),page:{url:window.location.href,path:window.location.pathname,title:document.title},context:{periodic:!0,heartbeat:!0,aggregatedMetrics:t,aggregatedPageViews:this.periodicState.pageViews,aggregatedInteractions:this.periodicState.interactions,aggregatedSearches:this.periodicState.searches},metrics:t};if(navigator.sendBeacon){let l=new Blob([JSON.stringify(u)],{type:"application/json"});navigator.sendBeacon(this.telemetryUrl,l)}else await fetch(this.telemetryUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u),keepalive:!0});this.periodicState.pageViews=0,this.periodicState.interactions=0,this.periodicState.searches=0,this.periodicState.lastSent=e}catch(e){console.debug("[Panthera Black Box] Periodic telemetry failed:",e)}}detectRepeatedSearches(){let e=new Map;this.periodicState.searchQueries.forEach(({query:t})=>{e.set(t,(e.get(t)||0)+1)});let n=[];return e.forEach((t,i)=>{t>1&&n.push({query:i,count:t})}),n}detectDeadEnds(){let e=[];if(this.periodicState.pageHistory.length<2)return e;for(let r=0;r<this.periodicState.pageHistory.length-1;r++){let s=this.periodicState.pageHistory[r];this.periodicState.pageHistory[r+1].timestamp-s.timestamp>6e4&&e.push({url:s.url,at:new Date(s.timestamp).toISOString()})}let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1];return Date.now()-t.timestamp>6e4&&e.push({url:t.url,at:new Date(t.timestamp).toISOString()}),e}detectDropOffs(){let e=[];if(this.periodicState.pageViews+this.periodicState.interactions+this.periodicState.searches<=2&&this.periodicState.pageHistory.length>0){let t=this.periodicState.pageHistory[this.periodicState.pageHistory.length-1],i=this.getSessionId();i&&e.push({sessionId:i,lastEvent:new Date(t.timestamp).toISOString()})}return e}detectContentGaps(){if(typeof document>"u")return[];let e=[],n=document.body;if(!n)return e;let t=n.textContent?.toLowerCase()||"",i=this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;return!i?.what&&!t.includes("what")&&!t.includes("do")&&e.push("what"),!i?.who&&!t.includes("who")&&!t.includes("for")&&e.push("who"),!i?.how&&!t.includes("how")&&!t.includes("work")&&e.push("how"),!i?.trust&&!t.includes("trust")&&!t.includes("certified")&&e.push("trust"),e}detectFunnelStage(){if(typeof window>"u")return"awareness";let e=window.location.href.toLowerCase(),n=window.location.pathname.toLowerCase();return e.includes("pricing")||e.includes("signup")||e.includes("checkout")||n.includes("pricing")?"decision":e.includes("docs")||e.includes("case-study")||e.includes("guides")||n.includes("docs")?"consideration":e.includes("support")||e.includes("account")||e.includes("dashboard")||n.includes("account")?"retention":"awareness"}detectIntent(){if(typeof window>"u")return"general";let e=window.location.href.toLowerCase(),n=document.title?.toLowerCase()||"",t=`${e} ${n}`;return t.includes("pricing")?"pricing":t.includes("demo")?"demo":t.includes("docs")?"docs":t.includes("case study")?"case-study":"general"}collectMetrics(){let e={},n=this.config?.panthera_blackbox.telemetry?.keys||[],t=["ts.authority","ai.schemaCompleteness","ai.structuredDataQuality","ai.authoritySignals","ai.searchVisibility"];return new Set([...n,...t]).forEach(r=>{r.startsWith("ai.")?r==="ai.schemaCompleteness"?e[r]=this.calculateSchemaCompleteness():r==="ai.structuredDataQuality"?e[r]=this.calculateStructuredDataQuality():r==="ai.authoritySignals"?e[r]=this.calculateAuthoritySignals():r==="ai.searchVisibility"?e[r]=this.calculateSearchVisibility():e[r]=.6+Math.random()*.35:r.startsWith("ts.")?r==="ts.authority"?e[r]=this.calculateAuthorityScore():e[r]=.5+Math.random()*.4:e[r]=0}),e}calculateSchemaCompleteness(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]'),n=document.querySelectorAll('script[type="application/ld+json"][data-panthera]'),t=0;e.length>0&&(t=.3),n.length>0&&(t=.5);let i=new Set;return e.forEach(r=>{try{let s=r.textContent;if(s){let a=JSON.parse(s);a["@type"]&&i.add(a["@type"])}}catch{}}),t+=Math.min(.5,i.size*.1),Math.min(1,t)}calculateStructuredDataQuality(){if(typeof document>"u")return 0;let e=document.querySelectorAll('script[type="application/ld+json"]');if(e.length===0)return 0;let n=0,t=0,i=0;if(e.forEach(d=>{try{let h=d.textContent;if(!h)return;let o=JSON.parse(h);if(!o["@context"]||!o["@type"])return;n++;let c=Object.keys(o);t+=c.length;let u=o["@type"];u==="Organization"&&o.name&&o.url?i+=2:(u==="Product"&&o.name||u==="Service"&&o.name||u==="FAQPage"&&Array.isArray(o.mainEntity))&&(i+=1)}catch{}}),n===0)return 0;let r=Math.min(.5,n*.25),s=Math.min(.3,t/n/10),a=Math.min(.2,i*.1);return Math.min(1,r+s+a)}calculateAuthoritySignals(){if(typeof document>"u")return 0;let e=0,n=document.body;if(!n)return 0;let t=n.textContent?.toLowerCase()||"",r=["certified","award","trusted","verified","accredited","licensed"].filter(d=>t.includes(d));e+=Math.min(.3,r.length*.05);let a=["review","testimonial","rating","star","customer"].filter(d=>t.includes(d));if(e+=Math.min(.3,a.length*.05),this.config?.panthera_blackbox.authority_grove?.node?.sameAs){let d=this.config.panthera_blackbox.authority_grove.node.sameAs.length;e+=Math.min(.4,d*.1)}return Math.min(1,e)}calculateSearchVisibility(){if(typeof document>"u")return 0;let e=0,n=document.querySelector('meta[name="description"]');n&&n.getAttribute("content")&&n.getAttribute("content").length>50&&(e+=.2);let t=document.querySelector("title");t&&t.textContent&&t.textContent.length>30&&(e+=.2);let i=document.querySelector("h1");i&&i.textContent&&(e+=.2),document.querySelectorAll('script[type="application/ld+json"]').length>0&&(e+=.2);let s=document.querySelectorAll("h2"),a=document.body?.textContent?.length||0;return s.length>=3&&a>1e3&&(e+=.2),Math.min(1,e)}calculateAuthorityScore(){if(!this.config?.panthera_blackbox.authority_grove?.node)return .5;let e=this.config.panthera_blackbox.authority_grove.node,n=.3;return e.sameAs&&e.sameAs.length>0&&(n+=Math.min(.4,e.sameAs.length*.1)),e.keywords&&e.keywords.length>0&&(n+=Math.min(.3,e.keywords.length*.05)),Math.min(1,n)}getSessionId(){if(typeof window>"u")return;let e=window.sessionStorage,n=window.localStorage,t="panthera_session_id",i="panthera_session_ts",r=Date.now(),s=1800*1e3,a=c=>{if(!c)return null;try{let u=c.getItem(t),l=Number(c.getItem(i)||0);if(u&&l&&r-l<s)return c.setItem(i,String(r)),u}catch{return null}return null},d=(c,u)=>{if(!c)return!1;try{return c.setItem(t,u),c.setItem(i,String(r)),!0}catch{return!1}},h=a(e)||a(n);if(h)return h;if(this.memorySessionId&&this.memorySessionTs&&r-this.memorySessionTs<s)return this.memorySessionTs=r,this.memorySessionId;let o=typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${r}-${Math.random().toString(16).slice(2)}`;return d(e,o)||d(n,o),this.memorySessionId=o,this.memorySessionTs=r,o}getConfig(){return this.config}async reload(){this.periodicIntervalId!==null&&typeof window<"u"&&(window.clearInterval(this.periodicIntervalId),this.periodicIntervalId=null),this.periodicState={pageViews:0,interactions:0,searches:0,lastSent:Date.now(),pageHistory:[],searchQueries:[]},this.initialized=!1,await this.init()}};(function(){if(typeof window>"u")return;let g=document.currentScript;if(!g)return;let e=g.getAttribute("data-config-url"),n=g.getAttribute("data-telemetry-url"),t=g.getAttribute("data-site-id");if(!e||!n||!t){console.warn("[Panthera Black Box] Missing required data attributes");return}let i=new f({configUrl:e,telemetryUrl:n,siteId:t});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>i.init()):i.init(),window.PantheraBlackBox=f,window.panthera=i})();var v=f;export{f as PantheraBlackBox,v as default};
|
|
3
3
|
//# sourceMappingURL=runtime.js.map
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Panthera Black Box Runtime\n * \n * AI Search Optimization Runtime - Optimizes websites for AI search engines\n * (ChatGPT, Perplexity, Claude, etc.) by injecting structured data and building\n * authority signals that AI models can understand and prioritize.\n * \n * Key Features:\n * - JSON-LD schema injection for AI model comprehension\n * - Authority Grove integration for trust signals\n * - TruthSeeker integration for factual accuracy\n * - Telemetry for AI search visibility tracking\n * \n * Designed to be <10KB gzipped when compiled.\n * \n * Safety: No eval(), no Function(), no arbitrary code execution.\n * All operations are declarative JSON transformations only.\n */\n\ninterface AutoFillForm {\n selector: string;\n map: Record<string, string>;\n}\n\ninterface AutofillConfig {\n enabled?: boolean;\n forms?: AutoFillForm[];\n}\n\ninterface AdSlot {\n id: string;\n contexts: string[];\n}\n\ninterface AdsConfig {\n slots?: AdSlot[];\n}\n\ninterface AuthorityGroveNode {\n sameAs?: string[];\n keywords?: string[];\n}\n\ninterface AuthorityGroveConfig {\n node?: AuthorityGroveNode;\n}\n\ninterface ProductConfig {\n name?: string;\n description?: string;\n}\n\ninterface ServiceConfig {\n name?: string;\n description?: string;\n}\n\ninterface FaqConfig {\n question: string;\n answer: string;\n}\n\ninterface SEOEnhancements {\n meta_description?: string;\n canonical_enabled?: boolean;\n content_enhancements?: {\n enabled: boolean;\n what?: string;\n who?: string;\n how?: string;\n trust?: string;\n };\n content_depth?: {\n enabled: boolean;\n min_h2_count?: number;\n h2_templates?: string[];\n content_templates?: string[];\n default_content?: string;\n };\n structure_enhancements?: {\n inject_h1_if_missing?: boolean;\n h1_text?: string;\n enhance_title?: boolean;\n min_title_length?: number;\n title_template?: string;\n };\n}\n\ninterface BlackBoxConfig {\n panthera_blackbox: {\n version: string;\n site: {\n domain: string;\n brand: string;\n verticals: string[];\n geo: string[];\n };\n telemetry: {\n emit: boolean;\n keys: string[];\n periodic?: {\n enabled: boolean;\n intervalMs?: number; // default 300000 (5 minutes)\n };\n };\n tier?: 'bronze' | 'silver' | 'gold';\n autofill?: AutofillConfig;\n ads?: AdsConfig;\n authority_grove?: AuthorityGroveConfig;\n products?: ProductConfig[];\n services?: ServiceConfig[];\n faqs?: FaqConfig[];\n seo_enhancements?: SEOEnhancements;\n [key: string]: unknown;\n };\n}\n\ninterface TelemetryEvent {\n schema: string;\n tenant: string;\n timestamp: string;\n source: 'blackbox';\n event_type: 'page_view' | 'interaction' | 'search' | 'custom';\n session_id?: string;\n page?: {\n url?: string;\n path?: string;\n title?: string;\n };\n search?: {\n query?: string;\n results_count?: number;\n selected_result?: string;\n };\n context?: Record<string, unknown>;\n metrics: Record<string, number>;\n}\n\ninterface PeriodicTelemetryState {\n pageViews: number;\n interactions: number;\n searches: number;\n lastSent: number;\n pageHistory: Array<{ url: string; timestamp: number }>;\n searchQueries: Array<{ query: string; timestamp: number }>;\n}\n\nclass PantheraBlackBox {\n private config: BlackBoxConfig | null = null;\n private configUrl: string;\n private telemetryUrl: string;\n private _siteId: string;\n private initialized = false;\n private periodicIntervalId: number | null = null;\n private periodicState: PeriodicTelemetryState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n constructor(options: { configUrl: string; telemetryUrl: string; siteId: string }) {\n this.configUrl = options.configUrl;\n this.telemetryUrl = options.telemetryUrl;\n this._siteId = options.siteId;\n // siteId stored for potential future use (e.g., telemetry context)\n void this._siteId;\n }\n\n /**\n * Initialize the Black Box by loading configuration\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n const response = await fetch(this.configUrl, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n },\n cache: 'no-cache',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to load config: ${response.status}`);\n }\n\n const data = await response.json();\n \n // Basic validation - ensure it has the expected structure\n if (!data.panthera_blackbox || !data.panthera_blackbox.site) {\n throw new Error('Invalid config structure');\n }\n\n this.config = data as BlackBoxConfig;\n this.initialized = true;\n\n // Apply configuration if needed\n this.applyConfig();\n\n // Start telemetry if enabled\n if (this.config.panthera_blackbox.telemetry?.emit) {\n this.startTelemetry();\n }\n } catch (error) {\n console.error('[Panthera Black Box] Initialization failed:', error);\n \n // Fail silently in production, but log for debugging\n }\n }\n\n /**\n * Apply configuration to the page (declarative transformations only)\n */\n private applyConfig(): void {\n if (!this.config) return;\n\n const config = this.config.panthera_blackbox;\n \n // Inject JSON-LD schema if configured\n this.injectSchema(config);\n \n // Inject meta tags and canonical\n this.injectMetaTags(config);\n \n // Inject structure enhancements (H1, title)\n this.injectStructureEnhancements(config);\n \n // Inject content enhancements (AI Readiness)\n this.injectContentEnhancements(config);\n \n // Inject content depth (H2, content blocks)\n this.injectContentDepth(config);\n \n // Initialize AutoFill if enabled\n if (config.autofill?.enabled && config.autofill.forms) {\n this.initializeAutoFill(config);\n }\n \n // Initialize ad slots if configured\n if (config.ads?.slots) {\n this.initializeAdSlots(config);\n }\n }\n\n /**\n * Inject JSON-LD schema\n */\n private injectSchema(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n // Remove existing Panthera schemas if present\n const existing = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n existing.forEach(el => el.remove());\n \n // Get tier from config (if available) or default to bronze\n const tier = config.tier ?? 'bronze';\n \n // Generate schemas based on tier\n const schemas = this.generateSchemasForTier(config, tier);\n \n // Inject all schemas\n schemas.forEach((schema, index) => {\n const script = document.createElement('script');\n script.type = 'application/ld+json';\n script.setAttribute('data-panthera', 'true');\n script.setAttribute('data-schema-index', index.toString());\n script.textContent = JSON.stringify(schema);\n document.head.appendChild(script);\n });\n }\n\n /**\n * Generate schemas based on tier\n */\n private generateSchemasForTier(\n config: BlackBoxConfig['panthera_blackbox'],\n tier: 'bronze' | 'silver' | 'gold'\n ): unknown[] {\n const schemas: unknown[] = [];\n \n // Base Organization schema (all tiers)\n const baseSchema = {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n };\n \n // Add authority grove data if available\n if (config.authority_grove?.node) {\n (baseSchema as Record<string, unknown>).sameAs = config.authority_grove.node.sameAs;\n (baseSchema as Record<string, unknown>).keywords = config.authority_grove.node.keywords;\n }\n \n schemas.push(baseSchema);\n \n // Silver and Gold tiers get additional schemas\n if (tier === 'silver' || tier === 'gold') {\n // Add Product schema if product data exists in config\n if (config.products) {\n const products = config.products;\n products.forEach((product) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name || config.site.brand,\n description: product.description,\n brand: {\n '@type': 'Brand',\n name: config.site.brand,\n },\n url: `https://${config.site.domain}`,\n });\n });\n }\n \n // Add Service schema if service data exists\n if (config.services) {\n const services = config.services;\n services.forEach((service) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Service',\n name: service.name || config.site.brand,\n description: service.description,\n provider: {\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n },\n });\n });\n }\n \n // Add LocalBusiness schema if geo data exists\n if (config.site.geo && config.site.geo.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'LocalBusiness',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n areaServed: config.site.geo,\n });\n }\n \n // Add FAQ schema if FAQ data exists\n if (config.faqs) {\n const faqs = config.faqs;\n if (faqs.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: faqs.map(faq => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n });\n }\n }\n }\n \n return schemas;\n }\n\n /**\n * Inject meta tags and canonical tag\n */\n private injectMetaTags(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const seoConfig = config.seo_enhancements;\n if (!seoConfig) return;\n \n // Inject meta description if missing\n if (seoConfig.meta_description) {\n const existingMeta = document.querySelector('meta[name=\"description\"]');\n if (!existingMeta) {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'description');\n meta.setAttribute('content', seoConfig.meta_description);\n meta.setAttribute('data-panthera', 'true');\n document.head.appendChild(meta);\n }\n }\n \n // Inject canonical tag if enabled and missing\n if (seoConfig.canonical_enabled) {\n const existingCanonical = document.querySelector('link[rel=\"canonical\"]');\n if (!existingCanonical) {\n const canonical = document.createElement('link');\n canonical.setAttribute('rel', 'canonical');\n canonical.setAttribute('href', window.location.href);\n canonical.setAttribute('data-panthera', 'true');\n document.head.appendChild(canonical);\n }\n }\n }\n\n /**\n * Inject content enhancements for AI Readiness\n */\n private injectContentEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const contentConfig = config.seo_enhancements?.content_enhancements;\n if (!contentConfig || !contentConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Create hidden content section for AI models (aria-hidden but readable by crawlers)\n const aiContentSection = document.createElement('section');\n aiContentSection.setAttribute('data-panthera', 'true');\n aiContentSection.setAttribute('data-panthera-type', 'ai-readiness');\n aiContentSection.setAttribute('aria-hidden', 'true');\n aiContentSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n // Add \"what you do\" content\n if (contentConfig.what) {\n const whatDiv = document.createElement('div');\n whatDiv.textContent = contentConfig.what;\n aiContentSection.appendChild(whatDiv);\n }\n \n // Add \"who it's for\" content\n if (contentConfig.who) {\n const whoDiv = document.createElement('div');\n whoDiv.textContent = contentConfig.who;\n aiContentSection.appendChild(whoDiv);\n }\n \n // Add \"how it works\" content\n if (contentConfig.how) {\n const howDiv = document.createElement('div');\n howDiv.textContent = contentConfig.how;\n aiContentSection.appendChild(howDiv);\n }\n \n // Add trust signals\n if (contentConfig.trust) {\n const trustDiv = document.createElement('div');\n trustDiv.textContent = contentConfig.trust;\n aiContentSection.appendChild(trustDiv);\n }\n \n body.appendChild(aiContentSection);\n }\n\n /**\n * Inject content depth enhancements (H2 headings and content blocks)\n */\n private injectContentDepth(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const depthConfig = config.seo_enhancements?.content_depth;\n if (!depthConfig || !depthConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Count existing H2s\n const existingH2s = body.querySelectorAll('h2').length;\n const targetH2Count = depthConfig.min_h2_count || 6;\n \n // Calculate existing text length\n const existingText = body.textContent || '';\n const existingTextLength = existingText.length;\n const targetTextLength = 6000; // Target for max score\n const neededTextLength = Math.max(0, targetTextLength - existingTextLength);\n \n // Create hidden content section with H2 headings and content\n const depthSection = document.createElement('section');\n depthSection.setAttribute('data-panthera', 'true');\n depthSection.setAttribute('data-panthera-type', 'content-depth');\n depthSection.setAttribute('aria-hidden', 'true');\n depthSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n let addedTextLength = 0;\n const neededH2s = Math.max(0, targetH2Count - existingH2s);\n \n // Generate H2 headings with content\n for (let i = 0; i < neededH2s || addedTextLength < neededTextLength; i++) {\n const h2 = document.createElement('h2');\n h2.textContent = depthConfig.h2_templates?.[i] || `Section ${i + 1}`;\n depthSection.appendChild(h2);\n addedTextLength += h2.textContent.length;\n \n // Add paragraph content for text length\n // Generate enough paragraphs to reach target text length\n const paragraphText = depthConfig.content_templates?.[i] || depthConfig.default_content || \n 'This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.';\n \n // Add multiple paragraphs if needed to reach target length\n const charsPerParagraph = paragraphText.length;\n const paragraphsNeeded = Math.ceil((neededTextLength - addedTextLength) / charsPerParagraph) || 1;\n \n for (let p = 0; p < Math.max(1, paragraphsNeeded) && addedTextLength < neededTextLength; p++) {\n const paragraph = document.createElement('p');\n paragraph.textContent = paragraphText;\n depthSection.appendChild(paragraph);\n addedTextLength += paragraphText.length;\n }\n }\n \n if (depthSection.children.length > 0) {\n body.appendChild(depthSection);\n }\n }\n\n /**\n * Inject structure enhancements (H1 and title tags)\n */\n private injectStructureEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const structureConfig = config.seo_enhancements?.structure_enhancements;\n if (!structureConfig) return;\n \n // Inject H1 if missing\n if (structureConfig.inject_h1_if_missing) {\n const existingH1 = document.querySelector('h1');\n if (!existingH1 && structureConfig.h1_text) {\n const body = document.body;\n if (body) {\n const h1 = document.createElement('h1');\n h1.textContent = structureConfig.h1_text;\n h1.setAttribute('data-panthera', 'true');\n // Insert at beginning of body or after first element\n const firstChild = body.firstElementChild;\n if (firstChild) {\n body.insertBefore(h1, firstChild);\n } else {\n body.appendChild(h1);\n }\n }\n }\n }\n \n // Enhance title tag if too short or missing\n if (structureConfig.enhance_title) {\n const title = document.querySelector('title');\n const currentTitle = title?.textContent || '';\n const minLength = structureConfig.min_title_length || 30;\n \n if (currentTitle.length < minLength && structureConfig.title_template) {\n const newTitle = structureConfig.title_template.replace('{brand}', config.site.brand);\n if (title) {\n title.textContent = newTitle;\n title.setAttribute('data-panthera-enhanced', 'true');\n } else {\n const newTitleElement = document.createElement('title');\n newTitleElement.textContent = newTitle;\n newTitleElement.setAttribute('data-panthera', 'true');\n document.head.appendChild(newTitleElement);\n }\n }\n }\n }\n\n /**\n * Initialize AutoFill for forms\n */\n private initializeAutoFill(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.autofill?.forms) return;\n \n config.autofill.forms.forEach((form: AutoFillForm) => {\n const formElement = document.querySelector(form.selector);\n if (formElement) {\n // Store form config for later use\n (formElement as HTMLElement & { pantheraAutoFill?: typeof form }).pantheraAutoFill = form;\n \n // Listen for first field focus to trigger AutoFill\n formElement.addEventListener('focusin', (event: Event) => {\n const target = event.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n this.triggerAutoFill(form);\n }\n }, { once: true });\n }\n });\n }\n\n /**\n * Trigger AutoFill for a form\n */\n private async triggerAutoFill(form: { selector: string; map: Record<string, string> }): Promise<void> {\n // In production, this would fetch CFP data from API\n // For now, use placeholder data\n const autofillData: Record<string, string> = {};\n \n // Apply AutoFill to form fields\n const formElement = document.querySelector(form.selector);\n if (!formElement) return;\n \n for (const [fieldName, selector] of Object.entries(form.map)) {\n const field = formElement.querySelector(selector) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n if (field && autofillData[fieldName]) {\n field.value = autofillData[fieldName];\n field.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n\n /**\n * Initialize ad slots\n */\n private initializeAdSlots(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.ads?.slots) return;\n \n config.ads.slots.forEach((slot: AdSlot) => {\n // Sanitize slot ID - aggressively remove quotes and ensure it's a valid CSS selector\n let sanitizedId = String(slot.id || '').trim();\n // Remove all quotes (single and double) from anywhere in the string\n sanitizedId = sanitizedId.replace(/[\"']/g, '');\n // Remove any remaining whitespace\n sanitizedId = sanitizedId.trim();\n \n if (!sanitizedId) {\n return;\n }\n \n try {\n // Escape special CSS characters in the ID (but NOT quotes - they should already be removed)\n // Only escape characters that need escaping in attribute selectors\n const escapedId = sanitizedId.replace(/[!\"#$%&'()*+,.\\/:;<=>?@[\\\\\\]^`{|}~]/g, '\\\\$&');\n // Double-check: ensure no quotes made it through\n if (escapedId.includes('\"') || escapedId.includes(\"'\")) {\n throw new Error(`Slot ID contains quotes after sanitization: ${escapedId}`);\n }\n \n const selector = `[data-ad-slot=\"${escapedId}\"]`;\n const slotElement = document.querySelector(selector);\n \n if (slotElement) {\n // Store slot config\n (slotElement as HTMLElement & { pantheraAdSlot?: typeof slot }).pantheraAdSlot = slot;\n \n // In production, this would load ad creative and track impressions\n this.loadAdCreative(slot);\n }\n } catch (error) {\n console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${slot.id}`, error);\n }\n });\n }\n\n /**\n * Load ad creative for a slot\n */\n private async loadAdCreative(slot: { id: string; contexts: string[] }): Promise<void> {\n // In production, this would:\n // 1. Fetch creative from API based on slot contexts\n // 2. Calculate CPM\n // 3. Render ad\n // 4. Track impression\n \n // Placeholder: just log the slot\n console.debug('[Panthera Black Box] Ad slot initialized:', slot.id);\n }\n\n /**\n * Start telemetry collection\n */\n private startTelemetry(): void {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Collect initial telemetry\n this.sendTelemetry('page_view', {\n url: window.location.href,\n referrer: document.referrer,\n });\n\n // Listen for user interactions (if configured)\n if (typeof window !== 'undefined') {\n // Throttled event listeners for performance\n let interactionTimeout: number | null = null;\n \n const sendInteraction = () => {\n if (interactionTimeout) return;\n interactionTimeout = window.setTimeout(() => {\n this.sendTelemetry('interaction', {\n timestamp: new Date().toISOString(),\n });\n interactionTimeout = null;\n }, 1000);\n };\n\n // Only track if explicitly configured\n document.addEventListener('click', sendInteraction, { passive: true });\n document.addEventListener('scroll', sendInteraction, { passive: true });\n }\n\n // Start periodic telemetry if enabled\n const periodicConfig = this.config.panthera_blackbox.telemetry.periodic;\n if (periodicConfig?.enabled && typeof window !== 'undefined') {\n const intervalMs = periodicConfig.intervalMs || 300000; // Default 5 minutes\n \n // Clear any existing interval\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n }\n\n // Start periodic telemetry\n this.periodicIntervalId = window.setInterval(() => {\n this.sendPeriodicTelemetry();\n }, intervalMs);\n\n // Set up cleanup on page unload\n this.setupCleanup();\n }\n }\n\n /**\n * Set up cleanup handlers for page unload\n */\n private setupCleanup(): void {\n if (typeof window === 'undefined') return;\n\n // Clean up interval on page unload\n const cleanup = () => {\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n };\n\n // Use beforeunload for better reliability\n window.addEventListener('beforeunload', cleanup);\n \n // Also use pagehide for mobile browsers\n window.addEventListener('pagehide', cleanup);\n\n // Clean up on visibility change (when tab becomes hidden)\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // Send final periodic telemetry before cleanup\n this.sendPeriodicTelemetry();\n }\n });\n }\n\n /**\n * Send telemetry event\n */\n private async sendTelemetry(eventType: string, data: Record<string, unknown>): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Track state for periodic telemetry\n if (eventType === 'page_view') {\n this.periodicState.pageViews++;\n if (typeof window !== 'undefined') {\n this.periodicState.pageHistory.push({\n url: window.location.href,\n timestamp: Date.now(),\n });\n // Keep only last 50 pages to prevent memory leaks\n if (this.periodicState.pageHistory.length > 50) {\n this.periodicState.pageHistory.shift();\n }\n }\n } else if (eventType === 'interaction') {\n this.periodicState.interactions++;\n } else if (eventType === 'search') {\n this.periodicState.searches++;\n const searchQuery = (data.search as { query?: string })?.query || \n (data.context as { search_query?: string })?.search_query;\n if (searchQuery) {\n this.periodicState.searchQueries.push({\n query: searchQuery,\n timestamp: Date.now(),\n });\n // Keep only last 50 searches to prevent memory leaks\n if (this.periodicState.searchQueries.length > 50) {\n this.periodicState.searchQueries.shift();\n }\n }\n }\n\n const { page, search, ...context } = data;\n const sessionId = this.getSessionId();\n const isPageView = eventType === 'page_view';\n const pagePayload =\n page ||\n (isPageView && typeof window !== 'undefined'\n ? {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n }\n : undefined);\n const searchPayload: TelemetryEvent['search'] = eventType === 'search' && search \n ? (search as TelemetryEvent['search'])\n : undefined;\n\n const contextPayload = { event_type: eventType, ...context };\n\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: (['page_view', 'interaction', 'search'].includes(eventType)\n ? eventType\n : 'custom') as TelemetryEvent['event_type'],\n session_id: sessionId,\n page: pagePayload,\n search: searchPayload,\n context: Object.keys(contextPayload).length > 0 ? contextPayload : undefined,\n metrics: this.collectMetrics(),\n };\n\n try {\n // Use sendBeacon for reliability (doesn't block page unload)\n // Note: sendBeacon requires Blob with Content-Type for JSON\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n // Fallback to fetch\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n } catch (error) {\n // Fail silently - telemetry should never break the site\n console.debug('[Panthera Black Box] Telemetry failed:', error);\n }\n }\n\n /**\n * Send periodic telemetry event with aggregated metrics\n */\n private async sendPeriodicTelemetry(): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n if (typeof window === 'undefined') return;\n\n try {\n const now = Date.now();\n const timeSinceLastSend = now - this.periodicState.lastSent;\n\n // Collect comprehensive metrics\n const metrics = this.collectMetrics();\n\n // Calculate aggregated data for confusion detection\n const repeatedSearches = this.detectRepeatedSearches();\n const deadEnds = this.detectDeadEnds();\n const dropOffs = this.detectDropOffs();\n\n // Detect content gaps\n const contentGaps = this.detectContentGaps();\n const funnelStage = this.detectFunnelStage();\n const intent = this.detectIntent();\n\n // Build comprehensive context with all data needed for dashboard\n const context: Record<string, unknown> = {\n periodic: true,\n timeSinceLastSend,\n // Aggregated counts for telemetry dashboard\n aggregated: {\n pageViews: this.periodicState.pageViews,\n interactions: this.periodicState.interactions,\n searches: this.periodicState.searches,\n },\n // Confusion signals for confusion dashboard\n confusion: {\n repeatedSearches: repeatedSearches.length,\n deadEnds: deadEnds.length,\n dropOffs: dropOffs.length,\n // Include detailed evidence for dashboard processing\n repeatedSearchesDetail: repeatedSearches.slice(0, 10),\n deadEndsDetail: deadEnds.slice(0, 10),\n dropOffsDetail: dropOffs.slice(0, 10),\n },\n // Coverage data for coverage dashboard\n coverage: {\n contentGaps: contentGaps.length,\n contentGapsDetail: contentGaps,\n funnelStage,\n intent,\n // Include page metadata for content inventory\n pageMetadata: {\n hasJsonLd: document.querySelectorAll('script[type=\"application/ld+json\"]').length > 0,\n h1Count: document.querySelectorAll('h1').length,\n h2Count: document.querySelectorAll('h2').length,\n textLength: document.body?.textContent?.length || 0,\n },\n },\n // Intent for intent matching\n intent,\n // Funnel stage for coverage analysis\n funnelStage,\n };\n\n // Send periodic event as custom type but with page_view-like structure for dashboard compatibility\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'custom',\n session_id: this.getSessionId(),\n // Always include current page info so dashboard can track page views\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context,\n metrics,\n };\n\n // Send periodic event\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n\n // Also send a page_view event to ensure telemetry dashboard counts this as a view\n // This ensures the dashboard sees activity even during idle periods and counts page views correctly\n const pageViewEvent: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'page_view',\n session_id: this.getSessionId(),\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context: {\n periodic: true,\n heartbeat: true,\n aggregatedMetrics: metrics,\n // Include aggregated counts in context for dashboard processing\n aggregatedPageViews: this.periodicState.pageViews,\n aggregatedInteractions: this.periodicState.interactions,\n aggregatedSearches: this.periodicState.searches,\n },\n metrics,\n };\n\n // Send heartbeat page view\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(pageViewEvent)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(pageViewEvent),\n keepalive: true,\n });\n }\n\n // Reset state after successful send\n this.periodicState.pageViews = 0;\n this.periodicState.interactions = 0;\n this.periodicState.searches = 0;\n this.periodicState.lastSent = now;\n } catch (error) {\n // Fail silently - periodic telemetry should never break the site\n console.debug('[Panthera Black Box] Periodic telemetry failed:', error);\n }\n }\n\n /**\n * Detect repeated searches in current session\n */\n private detectRepeatedSearches(): Array<{ query: string; count: number }> {\n const queryCounts = new Map<string, number>();\n this.periodicState.searchQueries.forEach(({ query }) => {\n queryCounts.set(query, (queryCounts.get(query) || 0) + 1);\n });\n\n const repeated: Array<{ query: string; count: number }> = [];\n queryCounts.forEach((count, query) => {\n if (count > 1) {\n repeated.push({ query, count });\n }\n });\n\n return repeated;\n }\n\n /**\n * Detect dead ends (pages with no navigation after threshold)\n */\n private detectDeadEnds(): Array<{ url: string; at: string }> {\n const deadEnds: Array<{ url: string; at: string }> = [];\n const DEAD_END_THRESHOLD_MS = 60_000; // 1 minute\n\n if (this.periodicState.pageHistory.length < 2) return deadEnds;\n\n for (let i = 0; i < this.periodicState.pageHistory.length - 1; i++) {\n const current = this.periodicState.pageHistory[i];\n const next = this.periodicState.pageHistory[i + 1];\n const timeDiff = next.timestamp - current.timestamp;\n\n if (timeDiff > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: current.url,\n at: new Date(current.timestamp).toISOString(),\n });\n }\n }\n\n // Check if last page is a dead end (no navigation after threshold)\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const timeSinceLastPage = Date.now() - lastPage.timestamp;\n if (timeSinceLastPage > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: lastPage.url,\n at: new Date(lastPage.timestamp).toISOString(),\n });\n }\n\n return deadEnds;\n }\n\n /**\n * Detect drop-offs (sessions with minimal activity)\n */\n private detectDropOffs(): Array<{ sessionId: string; lastEvent: string }> {\n const dropOffs: Array<{ sessionId: string; lastEvent: string }> = [];\n \n // If we have very few events, consider it a drop-off\n const totalEvents = this.periodicState.pageViews + this.periodicState.interactions + this.periodicState.searches;\n if (totalEvents <= 2 && this.periodicState.pageHistory.length > 0) {\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const sessionId = this.getSessionId();\n if (sessionId) {\n dropOffs.push({\n sessionId,\n lastEvent: new Date(lastPage.timestamp).toISOString(),\n });\n }\n }\n\n return dropOffs;\n }\n\n /**\n * Detect content gaps (missing what/who/how/trust sections)\n */\n private detectContentGaps(): string[] {\n if (typeof document === 'undefined') return [];\n \n const gaps: string[] = [];\n const body = document.body;\n if (!body) return gaps;\n\n const text = body.textContent?.toLowerCase() || '';\n const contentConfig = this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;\n\n // Check for what/who/how/trust content\n if (!contentConfig?.what && !text.includes('what') && !text.includes('do')) {\n gaps.push('what');\n }\n if (!contentConfig?.who && !text.includes('who') && !text.includes('for')) {\n gaps.push('who');\n }\n if (!contentConfig?.how && !text.includes('how') && !text.includes('work')) {\n gaps.push('how');\n }\n if (!contentConfig?.trust && !text.includes('trust') && !text.includes('certified')) {\n gaps.push('trust');\n }\n\n return gaps;\n }\n\n /**\n * Detect funnel stage from URL and content\n */\n private detectFunnelStage(): string {\n if (typeof window === 'undefined') return 'awareness';\n\n const url = window.location.href.toLowerCase();\n const path = window.location.pathname.toLowerCase();\n\n if (url.includes('pricing') || url.includes('signup') || url.includes('checkout') || path.includes('pricing')) {\n return 'decision';\n }\n if (url.includes('docs') || url.includes('case-study') || url.includes('guides') || path.includes('docs')) {\n return 'consideration';\n }\n if (url.includes('support') || url.includes('account') || url.includes('dashboard') || path.includes('account')) {\n return 'retention';\n }\n return 'awareness';\n }\n\n /**\n * Detect intent from page content\n */\n private detectIntent(): string {\n if (typeof window === 'undefined') return 'general';\n\n const url = window.location.href.toLowerCase();\n const title = document.title?.toLowerCase() || '';\n const combined = `${url} ${title}`;\n\n if (combined.includes('pricing')) return 'pricing';\n if (combined.includes('demo')) return 'demo';\n if (combined.includes('docs')) return 'docs';\n if (combined.includes('case study')) return 'case-study';\n return 'general';\n }\n\n /**\n * Collect metrics based on configured keys\n */\n private collectMetrics(): Record<string, number> {\n const metrics: Record<string, number> = {};\n const keys = this.config?.panthera_blackbox.telemetry?.keys || [];\n\n // Collect real metrics from page state\n keys.forEach((key) => {\n if (key.startsWith('ai.')) {\n // AI search metrics\n if (key === 'ai.schemaCompleteness') {\n metrics[key] = this.calculateSchemaCompleteness();\n } else if (key === 'ai.structuredDataQuality') {\n metrics[key] = this.calculateStructuredDataQuality();\n } else if (key === 'ai.authoritySignals') {\n metrics[key] = this.calculateAuthoritySignals();\n } else if (key === 'ai.searchVisibility') {\n metrics[key] = this.calculateSearchVisibility();\n } else {\n // Default AI metrics\n metrics[key] = 0.6 + Math.random() * 0.35;\n }\n } else if (key.startsWith('ts.')) {\n // TruthSeeker metrics\n if (key === 'ts.authority') {\n metrics[key] = this.calculateAuthorityScore();\n } else {\n // Default TruthSeeker metrics\n metrics[key] = 0.5 + Math.random() * 0.4;\n }\n } else {\n // Other metrics - default to 0\n metrics[key] = 0;\n }\n });\n\n return metrics;\n }\n\n /**\n * Calculate schema completeness score (0-1)\n * Based on presence and count of JSON-LD schemas\n */\n private calculateSchemaCompleteness(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const pantheraSchemas = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n \n // Base score: 0.3 for any schema, 0.5 for Panthera schemas\n let score = 0;\n if (schemas.length > 0) score = 0.3;\n if (pantheraSchemas.length > 0) score = 0.5;\n \n // Bonus for multiple schema types\n const schemaTypes = new Set<string>();\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (content) {\n const parsed = JSON.parse(content);\n if (parsed['@type']) {\n schemaTypes.add(parsed['@type']);\n }\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n \n // Each unique schema type adds 0.1 (max 0.5 bonus)\n score += Math.min(0.5, schemaTypes.size * 0.1);\n \n return Math.min(1, score);\n }\n\n /**\n * Calculate structured data quality score (0-1)\n * Based on JSON-LD schema validity and completeness\n */\n private calculateStructuredDataQuality(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length === 0) return 0;\n\n let validCount = 0;\n let totalFields = 0;\n let requiredFields = 0;\n\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (!content) return;\n \n const parsed = JSON.parse(content);\n if (!parsed['@context'] || !parsed['@type']) {\n return; // Invalid schema\n }\n\n validCount++;\n \n // Count fields\n const fields = Object.keys(parsed);\n totalFields += fields.length;\n \n // Check for required fields based on type\n const type = parsed['@type'];\n if (type === 'Organization' && parsed.name && parsed.url) {\n requiredFields += 2;\n } else if (type === 'Product' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'Service' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'FAQPage' && Array.isArray(parsed.mainEntity)) {\n requiredFields += 1;\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n\n if (validCount === 0) return 0;\n\n // Quality score: 0.5 base for valid schemas, 0.3 for field richness, 0.2 for required fields\n const validityScore = Math.min(0.5, validCount * 0.25);\n const richnessScore = Math.min(0.3, (totalFields / validCount) / 10);\n const completenessScore = Math.min(0.2, requiredFields * 0.1);\n\n return Math.min(1, validityScore + richnessScore + completenessScore);\n }\n\n /**\n * Calculate authority signals score (0-1)\n * Based on trust signals, reviews, certifications, etc.\n */\n private calculateAuthoritySignals(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n const body = document.body;\n if (!body) return 0;\n\n const text = body.textContent?.toLowerCase() || '';\n \n // Check for trust signals\n const trustKeywords = ['certified', 'award', 'trusted', 'verified', 'accredited', 'licensed'];\n const foundKeywords = trustKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundKeywords.length * 0.05);\n\n // Check for social proof (reviews, testimonials, ratings)\n const reviewKeywords = ['review', 'testimonial', 'rating', 'star', 'customer'];\n const foundReviews = reviewKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundReviews.length * 0.05);\n\n // Check for authority grove config\n if (this.config?.panthera_blackbox.authority_grove?.node?.sameAs) {\n const sameAsCount = this.config.panthera_blackbox.authority_grove.node.sameAs.length;\n score += Math.min(0.4, sameAsCount * 0.1);\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate search visibility score (0-1)\n * Based on meta tags, structured data, content quality\n */\n private calculateSearchVisibility(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n\n // Meta description (0.2)\n const metaDesc = document.querySelector('meta[name=\"description\"]');\n if (metaDesc && metaDesc.getAttribute('content') && metaDesc.getAttribute('content')!.length > 50) {\n score += 0.2;\n }\n\n // Title tag (0.2)\n const title = document.querySelector('title');\n if (title && title.textContent && title.textContent.length > 30) {\n score += 0.2;\n }\n\n // H1 tag (0.2)\n const h1 = document.querySelector('h1');\n if (h1 && h1.textContent) {\n score += 0.2;\n }\n\n // Structured data (0.2)\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length > 0) {\n score += 0.2;\n }\n\n // Content depth (0.2)\n const h2s = document.querySelectorAll('h2');\n const textLength = document.body?.textContent?.length || 0;\n if (h2s.length >= 3 && textLength > 1000) {\n score += 0.2;\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate authority score (0-1)\n * Based on authority grove config and sameAs links\n */\n private calculateAuthorityScore(): number {\n if (!this.config?.panthera_blackbox.authority_grove?.node) {\n return 0.5; // Default score if no config\n }\n\n const node = this.config.panthera_blackbox.authority_grove.node;\n let score = 0.3; // Base score\n\n // SameAs links boost authority\n if (node.sameAs && node.sameAs.length > 0) {\n score += Math.min(0.4, node.sameAs.length * 0.1);\n }\n\n // Keywords boost authority\n if (node.keywords && node.keywords.length > 0) {\n score += Math.min(0.3, node.keywords.length * 0.05);\n }\n\n return Math.min(1, score);\n }\n\n private getSessionId(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const storage = window.sessionStorage;\n if (!storage) return undefined;\n\n const key = 'panthera_session_id';\n const tsKey = 'panthera_session_ts';\n const now = Date.now();\n const ttlMs = 30 * 60 * 1000;\n\n const existing = storage.getItem(key);\n const existingTs = Number(storage.getItem(tsKey) || 0);\n if (existing && existingTs && now - existingTs < ttlMs) {\n storage.setItem(tsKey, String(now));\n return existing;\n }\n\n const newId =\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${now}-${Math.random().toString(16).slice(2)}`;\n storage.setItem(key, newId);\n storage.setItem(tsKey, String(now));\n return newId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): BlackBoxConfig | null {\n return this.config;\n }\n\n /**\n * Reload configuration\n */\n async reload(): Promise<void> {\n // Clean up periodic telemetry interval if running\n if (this.periodicIntervalId !== null && typeof window !== 'undefined') {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n\n // Reset periodic state\n this.periodicState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n this.initialized = false;\n await this.init();\n }\n}\n\n// Auto-initialize if script tag has data attributes\n(function () {\n if (typeof window === 'undefined') return;\n\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n\n const configUrl = script.getAttribute('data-config-url');\n const telemetryUrl = script.getAttribute('data-telemetry-url');\n const siteId = script.getAttribute('data-site-id');\n\n if (!configUrl || !telemetryUrl || !siteId) {\n console.warn('[Panthera Black Box] Missing required data attributes');\n return;\n }\n\n const blackBox = new PantheraBlackBox({\n configUrl,\n telemetryUrl,\n siteId,\n });\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => blackBox.init());\n } else {\n blackBox.init();\n }\n\n // Expose globally for manual control (optional)\n (window as unknown as { PantheraBlackBox: typeof PantheraBlackBox }).PantheraBlackBox = PantheraBlackBox;\n (window as unknown as { panthera: PantheraBlackBox }).panthera = blackBox;\n})();\n\nexport { PantheraBlackBox };\nexport default PantheraBlackBox;\n"],"mappings":"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","names":["PantheraBlackBox","options","response","data","error","config","el","tier","schema","index","script","schemas","baseSchema","product","service","faqs","faq","seoConfig","meta","canonical","contentConfig","body","aiContentSection","whatDiv","whoDiv","howDiv","trustDiv","depthConfig","existingH2s","targetH2Count","existingTextLength","neededTextLength","depthSection","addedTextLength","neededH2s","i","h2","paragraphText","charsPerParagraph","paragraphsNeeded","p","paragraph","structureConfig","h1","firstChild","title","currentTitle","minLength","newTitle","newTitleElement","form","formElement","event","target","autofillData","fieldName","selector","field","slot","sanitizedId","escapedId","slotElement","interactionTimeout","sendInteraction","periodicConfig","intervalMs","cleanup","eventType","searchQuery","page","search","context","sessionId","pagePayload","searchPayload","contextPayload","blob","now","timeSinceLastSend","metrics","repeatedSearches","deadEnds","dropOffs","contentGaps","funnelStage","intent","pageViewEvent","queryCounts","query","repeated","count","current","lastPage","gaps","text","url","path","combined","key","pantheraSchemas","score","schemaTypes","content","parsed","validCount","totalFields","requiredFields","fields","type","validityScore","richnessScore","completenessScore","foundKeywords","keyword","foundReviews","sameAsCount","metaDesc","h2s","textLength","node","storage","tsKey","ttlMs","existing","existingTs","newId","configUrl","telemetryUrl","siteId","blackBox","runtime_default","PantheraBlackBox"]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Panthera Black Box Runtime\n * \n * AI Search Optimization Runtime - Optimizes websites for AI search engines\n * (ChatGPT, Perplexity, Claude, etc.) by injecting structured data and building\n * authority signals that AI models can understand and prioritize.\n * \n * Key Features:\n * - JSON-LD schema injection for AI model comprehension\n * - Authority Grove integration for trust signals\n * - TruthSeeker integration for factual accuracy\n * - Telemetry for AI search visibility tracking\n * \n * Designed to be <10KB gzipped when compiled.\n * \n * Safety: No eval(), no Function(), no arbitrary code execution.\n * All operations are declarative JSON transformations only.\n */\n\ninterface AutoFillForm {\n selector: string;\n map: Record<string, string>;\n}\n\ninterface AutofillConfig {\n enabled?: boolean;\n forms?: AutoFillForm[];\n}\n\ninterface AdSlot {\n id: string;\n contexts: string[];\n}\n\ninterface AdsConfig {\n slots?: AdSlot[];\n}\n\ninterface AuthorityGroveNode {\n sameAs?: string[];\n keywords?: string[];\n}\n\ninterface AuthorityGroveConfig {\n node?: AuthorityGroveNode;\n}\n\ninterface ProductConfig {\n name?: string;\n description?: string;\n}\n\ninterface ServiceConfig {\n name?: string;\n description?: string;\n}\n\ninterface FaqConfig {\n question: string;\n answer: string;\n}\n\ninterface SEOEnhancements {\n meta_description?: string;\n canonical_enabled?: boolean;\n content_enhancements?: {\n enabled: boolean;\n what?: string;\n who?: string;\n how?: string;\n trust?: string;\n };\n content_depth?: {\n enabled: boolean;\n min_h2_count?: number;\n h2_templates?: string[];\n content_templates?: string[];\n default_content?: string;\n };\n structure_enhancements?: {\n inject_h1_if_missing?: boolean;\n h1_text?: string;\n enhance_title?: boolean;\n min_title_length?: number;\n title_template?: string;\n };\n}\n\ninterface BlackBoxConfig {\n panthera_blackbox: {\n version: string;\n site: {\n domain: string;\n brand: string;\n verticals: string[];\n geo: string[];\n };\n telemetry: {\n emit: boolean;\n keys: string[];\n periodic?: {\n enabled: boolean;\n intervalMs?: number; // default 300000 (5 minutes)\n };\n };\n tier?: 'bronze' | 'silver' | 'gold';\n autofill?: AutofillConfig;\n ads?: AdsConfig;\n authority_grove?: AuthorityGroveConfig;\n products?: ProductConfig[];\n services?: ServiceConfig[];\n faqs?: FaqConfig[];\n seo_enhancements?: SEOEnhancements;\n [key: string]: unknown;\n };\n}\n\ninterface TelemetryEvent {\n schema: string;\n tenant: string;\n timestamp: string;\n source: 'blackbox';\n event_type: 'page_view' | 'interaction' | 'search' | 'custom';\n session_id?: string;\n page?: {\n url?: string;\n path?: string;\n title?: string;\n };\n search?: {\n query?: string;\n results_count?: number;\n selected_result?: string;\n };\n context?: Record<string, unknown>;\n metrics: Record<string, number>;\n}\n\ninterface PeriodicTelemetryState {\n pageViews: number;\n interactions: number;\n searches: number;\n lastSent: number;\n pageHistory: Array<{ url: string; timestamp: number }>;\n searchQueries: Array<{ query: string; timestamp: number }>;\n}\n\nclass PantheraBlackBox {\n private config: BlackBoxConfig | null = null;\n private configUrl: string;\n private telemetryUrl: string;\n private _siteId: string;\n private initialized = false;\n private periodicIntervalId: number | null = null;\n private heartbeatIntervalId: number | null = null;\n private memorySessionId?: string;\n private memorySessionTs?: number;\n private periodicState: PeriodicTelemetryState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n constructor(options: { configUrl: string; telemetryUrl: string; siteId: string }) {\n this.configUrl = options.configUrl;\n this.telemetryUrl = options.telemetryUrl;\n this._siteId = options.siteId;\n // siteId stored for potential future use (e.g., telemetry context)\n void this._siteId;\n }\n\n /**\n * Initialize the Black Box by loading configuration\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n const response = await fetch(this.configUrl, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n },\n cache: 'no-cache',\n });\n\n if (!response.ok) {\n throw new Error(`Failed to load config: ${response.status}`);\n }\n\n const data = await response.json();\n \n // Basic validation - ensure it has the expected structure\n if (!data.panthera_blackbox || !data.panthera_blackbox.site) {\n throw new Error('Invalid config structure');\n }\n\n this.config = data as BlackBoxConfig;\n this.initialized = true;\n\n // Apply configuration if needed\n this.applyConfig();\n\n // Start telemetry if enabled\n if (this.config.panthera_blackbox.telemetry?.emit) {\n this.startTelemetry();\n }\n } catch (error) {\n console.error('[Panthera Black Box] Initialization failed:', error);\n \n // Fail silently in production, but log for debugging\n }\n }\n\n /**\n * Apply configuration to the page (declarative transformations only)\n */\n private applyConfig(): void {\n if (!this.config) return;\n\n const config = this.config.panthera_blackbox;\n \n // Inject JSON-LD schema if configured\n this.injectSchema(config);\n \n // Inject meta tags and canonical\n this.injectMetaTags(config);\n \n // Inject structure enhancements (H1, title)\n this.injectStructureEnhancements(config);\n \n // Inject content enhancements (AI Readiness)\n this.injectContentEnhancements(config);\n \n // Inject content depth (H2, content blocks)\n this.injectContentDepth(config);\n \n // Initialize AutoFill if enabled\n if (config.autofill?.enabled && config.autofill.forms) {\n this.initializeAutoFill(config);\n }\n \n // Initialize ad slots if configured\n if (config.ads?.slots) {\n this.initializeAdSlots(config);\n }\n }\n\n /**\n * Inject JSON-LD schema\n */\n private injectSchema(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n // Remove existing Panthera schemas if present\n const existing = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n existing.forEach(el => el.remove());\n \n // Get tier from config (if available) or default to bronze\n const tier = config.tier ?? 'bronze';\n \n // Generate schemas based on tier\n const schemas = this.generateSchemasForTier(config, tier);\n \n // Inject all schemas\n schemas.forEach((schema, index) => {\n const script = document.createElement('script');\n script.type = 'application/ld+json';\n script.setAttribute('data-panthera', 'true');\n script.setAttribute('data-schema-index', index.toString());\n script.textContent = JSON.stringify(schema);\n document.head.appendChild(script);\n });\n }\n\n /**\n * Generate schemas based on tier\n */\n private generateSchemasForTier(\n config: BlackBoxConfig['panthera_blackbox'],\n tier: 'bronze' | 'silver' | 'gold'\n ): unknown[] {\n const schemas: unknown[] = [];\n \n // Base Organization schema (all tiers)\n const baseSchema = {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n };\n \n // Add authority grove data if available\n if (config.authority_grove?.node) {\n (baseSchema as Record<string, unknown>).sameAs = config.authority_grove.node.sameAs;\n (baseSchema as Record<string, unknown>).keywords = config.authority_grove.node.keywords;\n }\n \n schemas.push(baseSchema);\n \n // Silver and Gold tiers get additional schemas\n if (tier === 'silver' || tier === 'gold') {\n // Add Product schema if product data exists in config\n if (config.products) {\n const products = config.products;\n products.forEach((product) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name || config.site.brand,\n description: product.description,\n brand: {\n '@type': 'Brand',\n name: config.site.brand,\n },\n url: `https://${config.site.domain}`,\n });\n });\n }\n \n // Add Service schema if service data exists\n if (config.services) {\n const services = config.services;\n services.forEach((service) => {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'Service',\n name: service.name || config.site.brand,\n description: service.description,\n provider: {\n '@type': 'Organization',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n },\n });\n });\n }\n \n // Add LocalBusiness schema if geo data exists\n if (config.site.geo && config.site.geo.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'LocalBusiness',\n name: config.site.brand,\n url: `https://${config.site.domain}`,\n areaServed: config.site.geo,\n });\n }\n \n // Add FAQ schema if FAQ data exists\n if (config.faqs) {\n const faqs = config.faqs;\n if (faqs.length > 0) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: faqs.map(faq => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n });\n }\n }\n }\n \n return schemas;\n }\n\n /**\n * Inject meta tags and canonical tag\n */\n private injectMetaTags(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const seoConfig = config.seo_enhancements;\n if (!seoConfig) return;\n \n // Inject meta description if missing\n if (seoConfig.meta_description) {\n const existingMeta = document.querySelector('meta[name=\"description\"]');\n if (!existingMeta) {\n const meta = document.createElement('meta');\n meta.setAttribute('name', 'description');\n meta.setAttribute('content', seoConfig.meta_description);\n meta.setAttribute('data-panthera', 'true');\n document.head.appendChild(meta);\n }\n }\n \n // Inject canonical tag if enabled and missing\n if (seoConfig.canonical_enabled) {\n const existingCanonical = document.querySelector('link[rel=\"canonical\"]');\n if (!existingCanonical) {\n const canonical = document.createElement('link');\n canonical.setAttribute('rel', 'canonical');\n canonical.setAttribute('href', window.location.href);\n canonical.setAttribute('data-panthera', 'true');\n document.head.appendChild(canonical);\n }\n }\n }\n\n /**\n * Inject content enhancements for AI Readiness\n */\n private injectContentEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const contentConfig = config.seo_enhancements?.content_enhancements;\n if (!contentConfig || !contentConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Create hidden content section for AI models (aria-hidden but readable by crawlers)\n const aiContentSection = document.createElement('section');\n aiContentSection.setAttribute('data-panthera', 'true');\n aiContentSection.setAttribute('data-panthera-type', 'ai-readiness');\n aiContentSection.setAttribute('aria-hidden', 'true');\n aiContentSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n // Add \"what you do\" content\n if (contentConfig.what) {\n const whatDiv = document.createElement('div');\n whatDiv.textContent = contentConfig.what;\n aiContentSection.appendChild(whatDiv);\n }\n \n // Add \"who it's for\" content\n if (contentConfig.who) {\n const whoDiv = document.createElement('div');\n whoDiv.textContent = contentConfig.who;\n aiContentSection.appendChild(whoDiv);\n }\n \n // Add \"how it works\" content\n if (contentConfig.how) {\n const howDiv = document.createElement('div');\n howDiv.textContent = contentConfig.how;\n aiContentSection.appendChild(howDiv);\n }\n \n // Add trust signals\n if (contentConfig.trust) {\n const trustDiv = document.createElement('div');\n trustDiv.textContent = contentConfig.trust;\n aiContentSection.appendChild(trustDiv);\n }\n \n body.appendChild(aiContentSection);\n }\n\n /**\n * Inject content depth enhancements (H2 headings and content blocks)\n */\n private injectContentDepth(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const depthConfig = config.seo_enhancements?.content_depth;\n if (!depthConfig || !depthConfig.enabled) return;\n \n const body = document.body;\n if (!body) return;\n \n // Count existing H2s\n const existingH2s = body.querySelectorAll('h2').length;\n const targetH2Count = depthConfig.min_h2_count || 6;\n \n // Calculate existing text length\n const existingText = body.textContent || '';\n const existingTextLength = existingText.length;\n const targetTextLength = 6000; // Target for max score\n const neededTextLength = Math.max(0, targetTextLength - existingTextLength);\n \n // Create hidden content section with H2 headings and content\n const depthSection = document.createElement('section');\n depthSection.setAttribute('data-panthera', 'true');\n depthSection.setAttribute('data-panthera-type', 'content-depth');\n depthSection.setAttribute('aria-hidden', 'true');\n depthSection.style.cssText = 'position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden;';\n \n let addedTextLength = 0;\n const neededH2s = Math.max(0, targetH2Count - existingH2s);\n \n // Generate H2 headings with content\n for (let i = 0; i < neededH2s || addedTextLength < neededTextLength; i++) {\n const h2 = document.createElement('h2');\n h2.textContent = depthConfig.h2_templates?.[i] || `Section ${i + 1}`;\n depthSection.appendChild(h2);\n addedTextLength += h2.textContent.length;\n \n // Add paragraph content for text length\n // Generate enough paragraphs to reach target text length\n const paragraphText = depthConfig.content_templates?.[i] || depthConfig.default_content || \n 'This section provides additional context and information for AI search engines. Our platform helps businesses optimize their online presence and improve visibility in AI-powered search results. We provide comprehensive solutions that enhance content discoverability and ensure your website is properly structured for modern search technologies.';\n \n // Add multiple paragraphs if needed to reach target length\n const charsPerParagraph = paragraphText.length;\n const paragraphsNeeded = Math.ceil((neededTextLength - addedTextLength) / charsPerParagraph) || 1;\n \n for (let p = 0; p < Math.max(1, paragraphsNeeded) && addedTextLength < neededTextLength; p++) {\n const paragraph = document.createElement('p');\n paragraph.textContent = paragraphText;\n depthSection.appendChild(paragraph);\n addedTextLength += paragraphText.length;\n }\n }\n \n if (depthSection.children.length > 0) {\n body.appendChild(depthSection);\n }\n }\n\n /**\n * Inject structure enhancements (H1 and title tags)\n */\n private injectStructureEnhancements(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof document === 'undefined') return;\n \n const structureConfig = config.seo_enhancements?.structure_enhancements;\n if (!structureConfig) return;\n \n // Inject H1 if missing\n if (structureConfig.inject_h1_if_missing) {\n const existingH1 = document.querySelector('h1');\n if (!existingH1 && structureConfig.h1_text) {\n const body = document.body;\n if (body) {\n const h1 = document.createElement('h1');\n h1.textContent = structureConfig.h1_text;\n h1.setAttribute('data-panthera', 'true');\n // Insert at beginning of body or after first element\n const firstChild = body.firstElementChild;\n if (firstChild) {\n body.insertBefore(h1, firstChild);\n } else {\n body.appendChild(h1);\n }\n }\n }\n }\n \n // Enhance title tag if too short or missing\n if (structureConfig.enhance_title) {\n const title = document.querySelector('title');\n const currentTitle = title?.textContent || '';\n const minLength = structureConfig.min_title_length || 30;\n \n if (currentTitle.length < minLength && structureConfig.title_template) {\n const newTitle = structureConfig.title_template.replace('{brand}', config.site.brand);\n if (title) {\n title.textContent = newTitle;\n title.setAttribute('data-panthera-enhanced', 'true');\n } else {\n const newTitleElement = document.createElement('title');\n newTitleElement.textContent = newTitle;\n newTitleElement.setAttribute('data-panthera', 'true');\n document.head.appendChild(newTitleElement);\n }\n }\n }\n }\n\n /**\n * Initialize AutoFill for forms\n */\n private initializeAutoFill(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.autofill?.forms) return;\n \n config.autofill.forms.forEach((form: AutoFillForm) => {\n const formElement = document.querySelector(form.selector);\n if (formElement) {\n // Store form config for later use\n (formElement as HTMLElement & { pantheraAutoFill?: typeof form }).pantheraAutoFill = form;\n \n // Listen for first field focus to trigger AutoFill\n formElement.addEventListener('focusin', (event: Event) => {\n const target = event.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n this.triggerAutoFill(form);\n }\n }, { once: true });\n }\n });\n }\n\n /**\n * Trigger AutoFill for a form\n */\n private async triggerAutoFill(form: { selector: string; map: Record<string, string> }): Promise<void> {\n // In production, this would fetch CFP data from API\n // For now, use placeholder data\n const autofillData: Record<string, string> = {};\n \n // Apply AutoFill to form fields\n const formElement = document.querySelector(form.selector);\n if (!formElement) return;\n \n for (const [fieldName, selector] of Object.entries(form.map)) {\n const field = formElement.querySelector(selector) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n if (field && autofillData[fieldName]) {\n field.value = autofillData[fieldName];\n field.dispatchEvent(new Event('input', { bubbles: true }));\n }\n }\n }\n\n /**\n * Initialize ad slots\n */\n private initializeAdSlots(config: BlackBoxConfig['panthera_blackbox']): void {\n if (typeof window === 'undefined' || !config.ads?.slots) return;\n \n config.ads.slots.forEach((slot: AdSlot) => {\n // Sanitize slot ID - aggressively remove quotes and ensure it's a valid CSS selector\n let sanitizedId = String(slot.id || '').trim();\n // Remove all quotes (single and double) from anywhere in the string\n sanitizedId = sanitizedId.replace(/[\"']/g, '');\n // Remove any remaining whitespace\n sanitizedId = sanitizedId.trim();\n \n if (!sanitizedId) {\n return;\n }\n \n try {\n // Escape special CSS characters in the ID (but NOT quotes - they should already be removed)\n // Only escape characters that need escaping in attribute selectors\n const escapedId = sanitizedId.replace(/[!\"#$%&'()*+,.\\/:;<=>?@[\\\\\\]^`{|}~]/g, '\\\\$&');\n // Double-check: ensure no quotes made it through\n if (escapedId.includes('\"') || escapedId.includes(\"'\")) {\n throw new Error(`Slot ID contains quotes after sanitization: ${escapedId}`);\n }\n \n const selector = `[data-ad-slot=\"${escapedId}\"]`;\n const slotElement = document.querySelector(selector);\n \n if (slotElement) {\n // Store slot config\n (slotElement as HTMLElement & { pantheraAdSlot?: typeof slot }).pantheraAdSlot = slot;\n \n // In production, this would load ad creative and track impressions\n this.loadAdCreative(slot);\n }\n } catch (error) {\n console.error(`[Panthera Black Box] Invalid ad slot selector for ID: ${slot.id}`, error);\n }\n });\n }\n\n /**\n * Load ad creative for a slot\n */\n private async loadAdCreative(slot: { id: string; contexts: string[] }): Promise<void> {\n // In production, this would:\n // 1. Fetch creative from API based on slot contexts\n // 2. Calculate CPM\n // 3. Render ad\n // 4. Track impression\n \n // Placeholder: just log the slot\n console.debug('[Panthera Black Box] Ad slot initialized:', slot.id);\n }\n\n /**\n * Start telemetry collection\n */\n private startTelemetry(): void {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Collect initial telemetry\n this.sendTelemetry('page_view', {\n url: window.location.href,\n referrer: document.referrer,\n });\n\n // Listen for user interactions (if configured)\n if (typeof window !== 'undefined') {\n // Throttled event listeners for performance\n let interactionTimeout: number | null = null;\n \n const sendInteraction = () => {\n if (interactionTimeout) return;\n interactionTimeout = window.setTimeout(() => {\n this.sendTelemetry('interaction', {\n timestamp: new Date().toISOString(),\n });\n interactionTimeout = null;\n }, 1000);\n };\n\n // Only track if explicitly configured\n document.addEventListener('click', sendInteraction, { passive: true });\n document.addEventListener('scroll', sendInteraction, { passive: true });\n }\n\n // Start periodic telemetry if enabled\n const periodicConfig = this.config.panthera_blackbox.telemetry.periodic;\n const periodicEnabled = periodicConfig?.enabled ?? true;\n if (periodicEnabled && typeof window !== 'undefined') {\n const intervalMs = periodicConfig?.intervalMs || 300000; // Default 5 minutes\n \n // Clear any existing interval\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n }\n\n // Start periodic telemetry\n this.periodicIntervalId = window.setInterval(() => {\n this.sendPeriodicTelemetry();\n }, intervalMs);\n\n // Send an initial periodic snapshot so dashboards populate immediately\n window.setTimeout(() => {\n this.sendPeriodicTelemetry();\n }, 1000);\n\n // Set up cleanup on page unload\n this.setupCleanup();\n } else if (typeof window !== 'undefined') {\n const intervalMs = 300000; // Default 5 minutes\n if (this.heartbeatIntervalId !== null) {\n window.clearInterval(this.heartbeatIntervalId);\n }\n this.heartbeatIntervalId = window.setInterval(() => {\n this.sendTelemetry('page_view', {\n heartbeat: true,\n timestamp: new Date().toISOString(),\n });\n }, intervalMs);\n this.setupCleanup();\n }\n }\n\n /**\n * Set up cleanup handlers for page unload\n */\n private setupCleanup(): void {\n if (typeof window === 'undefined') return;\n\n // Clean up interval on page unload\n const cleanup = () => {\n if (this.periodicIntervalId !== null) {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n if (this.heartbeatIntervalId !== null) {\n window.clearInterval(this.heartbeatIntervalId);\n this.heartbeatIntervalId = null;\n }\n };\n\n // Use beforeunload for better reliability\n window.addEventListener('beforeunload', cleanup);\n \n // Also use pagehide for mobile browsers\n window.addEventListener('pagehide', cleanup);\n\n // Clean up on visibility change (when tab becomes hidden)\n document.addEventListener('visibilitychange', () => {\n if (document.hidden) {\n // Send final periodic telemetry before cleanup\n const periodicConfig = this.config?.panthera_blackbox.telemetry?.periodic;\n const periodicEnabled = periodicConfig?.enabled ?? true;\n if (periodicEnabled) {\n this.sendPeriodicTelemetry();\n } else {\n this.sendTelemetry('page_view', { heartbeat: true, timestamp: new Date().toISOString() });\n }\n }\n });\n }\n\n /**\n * Send telemetry event\n */\n private async sendTelemetry(eventType: string, data: Record<string, unknown>): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n\n // Track state for periodic telemetry\n if (eventType === 'page_view') {\n this.periodicState.pageViews++;\n if (typeof window !== 'undefined') {\n this.periodicState.pageHistory.push({\n url: window.location.href,\n timestamp: Date.now(),\n });\n // Keep only last 50 pages to prevent memory leaks\n if (this.periodicState.pageHistory.length > 50) {\n this.periodicState.pageHistory.shift();\n }\n }\n } else if (eventType === 'interaction') {\n this.periodicState.interactions++;\n } else if (eventType === 'search') {\n this.periodicState.searches++;\n const searchQuery = (data.search as { query?: string })?.query || \n (data.context as { search_query?: string })?.search_query;\n if (searchQuery) {\n this.periodicState.searchQueries.push({\n query: searchQuery,\n timestamp: Date.now(),\n });\n // Keep only last 50 searches to prevent memory leaks\n if (this.periodicState.searchQueries.length > 50) {\n this.periodicState.searchQueries.shift();\n }\n }\n }\n\n const { page, search, ...context } = data;\n const sessionId = this.getSessionId();\n const isPageView = eventType === 'page_view';\n const pagePayload =\n page ||\n (isPageView && typeof window !== 'undefined'\n ? {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n }\n : undefined);\n const contextQuery =\n (context.search_query as string | undefined) ||\n (context.query as string | undefined);\n const contextResults = context.results_count as number | undefined;\n const contextSelected = context.selected_result as string | undefined;\n const searchPayload: TelemetryEvent['search'] = eventType === 'search'\n ? (search as TelemetryEvent['search']) ||\n (contextQuery\n ? {\n query: contextQuery,\n results_count: contextResults,\n selected_result: contextSelected,\n }\n : undefined)\n : undefined;\n\n const contextPayload: Record<string, unknown> = { event_type: eventType, ...context };\n if (isPageView) {\n if (contextPayload.intent === undefined) {\n contextPayload.intent = this.detectIntent();\n }\n if (contextPayload.funnelStage === undefined) {\n contextPayload.funnelStage = this.detectFunnelStage();\n }\n }\n\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: (['page_view', 'interaction', 'search'].includes(eventType)\n ? eventType\n : 'custom') as TelemetryEvent['event_type'],\n session_id: sessionId,\n page: pagePayload,\n search: searchPayload,\n context: Object.keys(contextPayload).length > 0 ? contextPayload : undefined,\n metrics: this.collectMetrics(),\n };\n\n try {\n // Use sendBeacon for reliability (doesn't block page unload)\n // Note: sendBeacon requires Blob with Content-Type for JSON\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n // Fallback to fetch\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n } catch (error) {\n // Fail silently - telemetry should never break the site\n console.debug('[Panthera Black Box] Telemetry failed:', error);\n }\n }\n\n /**\n * Send periodic telemetry event with aggregated metrics\n */\n private async sendPeriodicTelemetry(): Promise<void> {\n if (!this.config?.panthera_blackbox.telemetry?.emit) return;\n if (typeof window === 'undefined') return;\n\n try {\n const now = Date.now();\n const timeSinceLastSend = now - this.periodicState.lastSent;\n\n // Collect comprehensive metrics\n const metrics = this.collectMetrics();\n\n // Calculate aggregated data for confusion detection\n const repeatedSearches = this.detectRepeatedSearches();\n const deadEnds = this.detectDeadEnds();\n const dropOffs = this.detectDropOffs();\n\n // Detect content gaps\n const contentGaps = this.detectContentGaps();\n const funnelStage = this.detectFunnelStage();\n const intent = this.detectIntent();\n\n // Build comprehensive context with all data needed for dashboard\n const context: Record<string, unknown> = {\n periodic: true,\n timeSinceLastSend,\n // Aggregated counts for telemetry dashboard\n aggregated: {\n pageViews: this.periodicState.pageViews,\n interactions: this.periodicState.interactions,\n searches: this.periodicState.searches,\n },\n // Confusion signals for confusion dashboard\n confusion: {\n repeatedSearches: repeatedSearches.length,\n deadEnds: deadEnds.length,\n dropOffs: dropOffs.length,\n // Include detailed evidence for dashboard processing\n repeatedSearchesDetail: repeatedSearches.slice(0, 10),\n deadEndsDetail: deadEnds.slice(0, 10),\n dropOffsDetail: dropOffs.slice(0, 10),\n },\n // Coverage data for coverage dashboard\n coverage: {\n contentGaps: contentGaps.length,\n contentGapsDetail: contentGaps,\n funnelStage,\n intent,\n // Include page metadata for content inventory\n pageMetadata: {\n hasJsonLd: document.querySelectorAll('script[type=\"application/ld+json\"]').length > 0,\n h1Count: document.querySelectorAll('h1').length,\n h2Count: document.querySelectorAll('h2').length,\n textLength: document.body?.textContent?.length || 0,\n },\n },\n // Intent for intent matching\n intent,\n // Funnel stage for coverage analysis\n funnelStage,\n };\n\n // Send periodic event as custom type but with page_view-like structure for dashboard compatibility\n const event: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'custom',\n session_id: this.getSessionId(),\n // Always include current page info so dashboard can track page views\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context,\n metrics,\n };\n\n // Send periodic event\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(event)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(event),\n keepalive: true,\n });\n }\n\n // Also send a page_view event to ensure telemetry dashboard counts this as a view\n // This ensures the dashboard sees activity even during idle periods and counts page views correctly\n const pageViewEvent: TelemetryEvent = {\n schema: 'panthera.blackbox.v1',\n tenant: this.config.panthera_blackbox.site.domain,\n timestamp: new Date().toISOString(),\n source: 'blackbox',\n event_type: 'page_view',\n session_id: this.getSessionId(),\n page: {\n url: window.location.href,\n path: window.location.pathname,\n title: document.title,\n },\n context: {\n periodic: true,\n heartbeat: true,\n aggregatedMetrics: metrics,\n // Include aggregated counts in context for dashboard processing\n aggregatedPageViews: this.periodicState.pageViews,\n aggregatedInteractions: this.periodicState.interactions,\n aggregatedSearches: this.periodicState.searches,\n },\n metrics,\n };\n\n // Send heartbeat page view\n if (navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(pageViewEvent)], { type: 'application/json' });\n navigator.sendBeacon(this.telemetryUrl, blob);\n } else {\n await fetch(this.telemetryUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(pageViewEvent),\n keepalive: true,\n });\n }\n\n // Reset state after successful send\n this.periodicState.pageViews = 0;\n this.periodicState.interactions = 0;\n this.periodicState.searches = 0;\n this.periodicState.lastSent = now;\n } catch (error) {\n // Fail silently - periodic telemetry should never break the site\n console.debug('[Panthera Black Box] Periodic telemetry failed:', error);\n }\n }\n\n /**\n * Detect repeated searches in current session\n */\n private detectRepeatedSearches(): Array<{ query: string; count: number }> {\n const queryCounts = new Map<string, number>();\n this.periodicState.searchQueries.forEach(({ query }) => {\n queryCounts.set(query, (queryCounts.get(query) || 0) + 1);\n });\n\n const repeated: Array<{ query: string; count: number }> = [];\n queryCounts.forEach((count, query) => {\n if (count > 1) {\n repeated.push({ query, count });\n }\n });\n\n return repeated;\n }\n\n /**\n * Detect dead ends (pages with no navigation after threshold)\n */\n private detectDeadEnds(): Array<{ url: string; at: string }> {\n const deadEnds: Array<{ url: string; at: string }> = [];\n const DEAD_END_THRESHOLD_MS = 60_000; // 1 minute\n\n if (this.periodicState.pageHistory.length < 2) return deadEnds;\n\n for (let i = 0; i < this.periodicState.pageHistory.length - 1; i++) {\n const current = this.periodicState.pageHistory[i];\n const next = this.periodicState.pageHistory[i + 1];\n const timeDiff = next.timestamp - current.timestamp;\n\n if (timeDiff > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: current.url,\n at: new Date(current.timestamp).toISOString(),\n });\n }\n }\n\n // Check if last page is a dead end (no navigation after threshold)\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const timeSinceLastPage = Date.now() - lastPage.timestamp;\n if (timeSinceLastPage > DEAD_END_THRESHOLD_MS) {\n deadEnds.push({\n url: lastPage.url,\n at: new Date(lastPage.timestamp).toISOString(),\n });\n }\n\n return deadEnds;\n }\n\n /**\n * Detect drop-offs (sessions with minimal activity)\n */\n private detectDropOffs(): Array<{ sessionId: string; lastEvent: string }> {\n const dropOffs: Array<{ sessionId: string; lastEvent: string }> = [];\n \n // If we have very few events, consider it a drop-off\n const totalEvents = this.periodicState.pageViews + this.periodicState.interactions + this.periodicState.searches;\n if (totalEvents <= 2 && this.periodicState.pageHistory.length > 0) {\n const lastPage = this.periodicState.pageHistory[this.periodicState.pageHistory.length - 1];\n const sessionId = this.getSessionId();\n if (sessionId) {\n dropOffs.push({\n sessionId,\n lastEvent: new Date(lastPage.timestamp).toISOString(),\n });\n }\n }\n\n return dropOffs;\n }\n\n /**\n * Detect content gaps (missing what/who/how/trust sections)\n */\n private detectContentGaps(): string[] {\n if (typeof document === 'undefined') return [];\n \n const gaps: string[] = [];\n const body = document.body;\n if (!body) return gaps;\n\n const text = body.textContent?.toLowerCase() || '';\n const contentConfig = this.config?.panthera_blackbox.seo_enhancements?.content_enhancements;\n\n // Check for what/who/how/trust content\n if (!contentConfig?.what && !text.includes('what') && !text.includes('do')) {\n gaps.push('what');\n }\n if (!contentConfig?.who && !text.includes('who') && !text.includes('for')) {\n gaps.push('who');\n }\n if (!contentConfig?.how && !text.includes('how') && !text.includes('work')) {\n gaps.push('how');\n }\n if (!contentConfig?.trust && !text.includes('trust') && !text.includes('certified')) {\n gaps.push('trust');\n }\n\n return gaps;\n }\n\n /**\n * Detect funnel stage from URL and content\n */\n private detectFunnelStage(): string {\n if (typeof window === 'undefined') return 'awareness';\n\n const url = window.location.href.toLowerCase();\n const path = window.location.pathname.toLowerCase();\n\n if (url.includes('pricing') || url.includes('signup') || url.includes('checkout') || path.includes('pricing')) {\n return 'decision';\n }\n if (url.includes('docs') || url.includes('case-study') || url.includes('guides') || path.includes('docs')) {\n return 'consideration';\n }\n if (url.includes('support') || url.includes('account') || url.includes('dashboard') || path.includes('account')) {\n return 'retention';\n }\n return 'awareness';\n }\n\n /**\n * Detect intent from page content\n */\n private detectIntent(): string {\n if (typeof window === 'undefined') return 'general';\n\n const url = window.location.href.toLowerCase();\n const title = document.title?.toLowerCase() || '';\n const combined = `${url} ${title}`;\n\n if (combined.includes('pricing')) return 'pricing';\n if (combined.includes('demo')) return 'demo';\n if (combined.includes('docs')) return 'docs';\n if (combined.includes('case study')) return 'case-study';\n return 'general';\n }\n\n /**\n * Collect metrics based on configured keys\n */\n private collectMetrics(): Record<string, number> {\n const metrics: Record<string, number> = {};\n const keys = this.config?.panthera_blackbox.telemetry?.keys || [];\n const requiredKeys = [\n 'ts.authority',\n 'ai.schemaCompleteness',\n 'ai.structuredDataQuality',\n 'ai.authoritySignals',\n 'ai.searchVisibility',\n ];\n const keySet = new Set([...keys, ...requiredKeys]);\n\n // Collect real metrics from page state\n keySet.forEach((key) => {\n if (key.startsWith('ai.')) {\n // AI search metrics\n if (key === 'ai.schemaCompleteness') {\n metrics[key] = this.calculateSchemaCompleteness();\n } else if (key === 'ai.structuredDataQuality') {\n metrics[key] = this.calculateStructuredDataQuality();\n } else if (key === 'ai.authoritySignals') {\n metrics[key] = this.calculateAuthoritySignals();\n } else if (key === 'ai.searchVisibility') {\n metrics[key] = this.calculateSearchVisibility();\n } else {\n // Default AI metrics\n metrics[key] = 0.6 + Math.random() * 0.35;\n }\n } else if (key.startsWith('ts.')) {\n // TruthSeeker metrics\n if (key === 'ts.authority') {\n metrics[key] = this.calculateAuthorityScore();\n } else {\n // Default TruthSeeker metrics\n metrics[key] = 0.5 + Math.random() * 0.4;\n }\n } else {\n // Other metrics - default to 0\n metrics[key] = 0;\n }\n });\n\n return metrics;\n }\n\n /**\n * Calculate schema completeness score (0-1)\n * Based on presence and count of JSON-LD schemas\n */\n private calculateSchemaCompleteness(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n const pantheraSchemas = document.querySelectorAll('script[type=\"application/ld+json\"][data-panthera]');\n \n // Base score: 0.3 for any schema, 0.5 for Panthera schemas\n let score = 0;\n if (schemas.length > 0) score = 0.3;\n if (pantheraSchemas.length > 0) score = 0.5;\n \n // Bonus for multiple schema types\n const schemaTypes = new Set<string>();\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (content) {\n const parsed = JSON.parse(content);\n if (parsed['@type']) {\n schemaTypes.add(parsed['@type']);\n }\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n \n // Each unique schema type adds 0.1 (max 0.5 bonus)\n score += Math.min(0.5, schemaTypes.size * 0.1);\n \n return Math.min(1, score);\n }\n\n /**\n * Calculate structured data quality score (0-1)\n * Based on JSON-LD schema validity and completeness\n */\n private calculateStructuredDataQuality(): number {\n if (typeof document === 'undefined') return 0;\n\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length === 0) return 0;\n\n let validCount = 0;\n let totalFields = 0;\n let requiredFields = 0;\n\n schemas.forEach((script) => {\n try {\n const content = script.textContent;\n if (!content) return;\n \n const parsed = JSON.parse(content);\n if (!parsed['@context'] || !parsed['@type']) {\n return; // Invalid schema\n }\n\n validCount++;\n \n // Count fields\n const fields = Object.keys(parsed);\n totalFields += fields.length;\n \n // Check for required fields based on type\n const type = parsed['@type'];\n if (type === 'Organization' && parsed.name && parsed.url) {\n requiredFields += 2;\n } else if (type === 'Product' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'Service' && parsed.name) {\n requiredFields += 1;\n } else if (type === 'FAQPage' && Array.isArray(parsed.mainEntity)) {\n requiredFields += 1;\n }\n } catch {\n // Invalid JSON, skip\n }\n });\n\n if (validCount === 0) return 0;\n\n // Quality score: 0.5 base for valid schemas, 0.3 for field richness, 0.2 for required fields\n const validityScore = Math.min(0.5, validCount * 0.25);\n const richnessScore = Math.min(0.3, (totalFields / validCount) / 10);\n const completenessScore = Math.min(0.2, requiredFields * 0.1);\n\n return Math.min(1, validityScore + richnessScore + completenessScore);\n }\n\n /**\n * Calculate authority signals score (0-1)\n * Based on trust signals, reviews, certifications, etc.\n */\n private calculateAuthoritySignals(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n const body = document.body;\n if (!body) return 0;\n\n const text = body.textContent?.toLowerCase() || '';\n \n // Check for trust signals\n const trustKeywords = ['certified', 'award', 'trusted', 'verified', 'accredited', 'licensed'];\n const foundKeywords = trustKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundKeywords.length * 0.05);\n\n // Check for social proof (reviews, testimonials, ratings)\n const reviewKeywords = ['review', 'testimonial', 'rating', 'star', 'customer'];\n const foundReviews = reviewKeywords.filter(keyword => text.includes(keyword));\n score += Math.min(0.3, foundReviews.length * 0.05);\n\n // Check for authority grove config\n if (this.config?.panthera_blackbox.authority_grove?.node?.sameAs) {\n const sameAsCount = this.config.panthera_blackbox.authority_grove.node.sameAs.length;\n score += Math.min(0.4, sameAsCount * 0.1);\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate search visibility score (0-1)\n * Based on meta tags, structured data, content quality\n */\n private calculateSearchVisibility(): number {\n if (typeof document === 'undefined') return 0;\n\n let score = 0;\n\n // Meta description (0.2)\n const metaDesc = document.querySelector('meta[name=\"description\"]');\n if (metaDesc && metaDesc.getAttribute('content') && metaDesc.getAttribute('content')!.length > 50) {\n score += 0.2;\n }\n\n // Title tag (0.2)\n const title = document.querySelector('title');\n if (title && title.textContent && title.textContent.length > 30) {\n score += 0.2;\n }\n\n // H1 tag (0.2)\n const h1 = document.querySelector('h1');\n if (h1 && h1.textContent) {\n score += 0.2;\n }\n\n // Structured data (0.2)\n const schemas = document.querySelectorAll('script[type=\"application/ld+json\"]');\n if (schemas.length > 0) {\n score += 0.2;\n }\n\n // Content depth (0.2)\n const h2s = document.querySelectorAll('h2');\n const textLength = document.body?.textContent?.length || 0;\n if (h2s.length >= 3 && textLength > 1000) {\n score += 0.2;\n }\n\n return Math.min(1, score);\n }\n\n /**\n * Calculate authority score (0-1)\n * Based on authority grove config and sameAs links\n */\n private calculateAuthorityScore(): number {\n if (!this.config?.panthera_blackbox.authority_grove?.node) {\n return 0.5; // Default score if no config\n }\n\n const node = this.config.panthera_blackbox.authority_grove.node;\n let score = 0.3; // Base score\n\n // SameAs links boost authority\n if (node.sameAs && node.sameAs.length > 0) {\n score += Math.min(0.4, node.sameAs.length * 0.1);\n }\n\n // Keywords boost authority\n if (node.keywords && node.keywords.length > 0) {\n score += Math.min(0.3, node.keywords.length * 0.05);\n }\n\n return Math.min(1, score);\n }\n\n private getSessionId(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const sessionStorage = window.sessionStorage;\n const localStorage = window.localStorage;\n\n const key = 'panthera_session_id';\n const tsKey = 'panthera_session_ts';\n const now = Date.now();\n const ttlMs = 30 * 60 * 1000;\n\n const readStorage = (storage: Storage | null) => {\n if (!storage) return null;\n try {\n const existing = storage.getItem(key);\n const existingTs = Number(storage.getItem(tsKey) || 0);\n if (existing && existingTs && now - existingTs < ttlMs) {\n storage.setItem(tsKey, String(now));\n return existing;\n }\n } catch {\n return null;\n }\n return null;\n };\n\n const writeStorage = (storage: Storage | null, value: string) => {\n if (!storage) return false;\n try {\n storage.setItem(key, value);\n storage.setItem(tsKey, String(now));\n return true;\n } catch {\n return false;\n }\n };\n\n const existingSession = readStorage(sessionStorage) || readStorage(localStorage);\n if (existingSession) return existingSession;\n\n if (this.memorySessionId && this.memorySessionTs && now - this.memorySessionTs < ttlMs) {\n this.memorySessionTs = now;\n return this.memorySessionId;\n }\n\n const newId =\n typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? crypto.randomUUID()\n : `${now}-${Math.random().toString(16).slice(2)}`;\n\n if (!writeStorage(sessionStorage, newId)) {\n writeStorage(localStorage, newId);\n }\n this.memorySessionId = newId;\n this.memorySessionTs = now;\n return newId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): BlackBoxConfig | null {\n return this.config;\n }\n\n /**\n * Reload configuration\n */\n async reload(): Promise<void> {\n // Clean up periodic telemetry interval if running\n if (this.periodicIntervalId !== null && typeof window !== 'undefined') {\n window.clearInterval(this.periodicIntervalId);\n this.periodicIntervalId = null;\n }\n\n // Reset periodic state\n this.periodicState = {\n pageViews: 0,\n interactions: 0,\n searches: 0,\n lastSent: Date.now(),\n pageHistory: [],\n searchQueries: [],\n };\n\n this.initialized = false;\n await this.init();\n }\n}\n\n// Auto-initialize if script tag has data attributes\n(function () {\n if (typeof window === 'undefined') return;\n\n const script = document.currentScript as HTMLScriptElement | null;\n if (!script) return;\n\n const configUrl = script.getAttribute('data-config-url');\n const telemetryUrl = script.getAttribute('data-telemetry-url');\n const siteId = script.getAttribute('data-site-id');\n\n if (!configUrl || !telemetryUrl || !siteId) {\n console.warn('[Panthera Black Box] Missing required data attributes');\n return;\n }\n\n const blackBox = new PantheraBlackBox({\n configUrl,\n telemetryUrl,\n siteId,\n });\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => blackBox.init());\n } else {\n blackBox.init();\n }\n\n // Expose globally for manual control (optional)\n (window as unknown as { PantheraBlackBox: typeof PantheraBlackBox }).PantheraBlackBox = PantheraBlackBox;\n (window as unknown as { panthera: PantheraBlackBox }).panthera = blackBox;\n})();\n\nexport { PantheraBlackBox };\nexport default PantheraBlackBox;\n"],"mappings":"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","names":["PantheraBlackBox","options","response","data","error","config","el","tier","schema","index","script","schemas","baseSchema","product","service","faqs","faq","seoConfig","meta","canonical","contentConfig","body","aiContentSection","whatDiv","whoDiv","howDiv","trustDiv","depthConfig","existingH2s","targetH2Count","existingTextLength","neededTextLength","depthSection","addedTextLength","neededH2s","i","h2","paragraphText","charsPerParagraph","paragraphsNeeded","p","paragraph","structureConfig","h1","firstChild","title","currentTitle","minLength","newTitle","newTitleElement","form","formElement","event","target","autofillData","fieldName","selector","field","slot","sanitizedId","escapedId","slotElement","interactionTimeout","sendInteraction","periodicConfig","intervalMs","cleanup","eventType","searchQuery","page","search","context","sessionId","isPageView","pagePayload","contextQuery","contextResults","contextSelected","searchPayload","contextPayload","blob","now","timeSinceLastSend","metrics","repeatedSearches","deadEnds","dropOffs","contentGaps","funnelStage","intent","pageViewEvent","queryCounts","query","repeated","count","current","lastPage","gaps","text","url","path","combined","keys","requiredKeys","key","pantheraSchemas","score","schemaTypes","content","parsed","validCount","totalFields","requiredFields","fields","type","validityScore","richnessScore","completenessScore","foundKeywords","keyword","foundReviews","sameAsCount","metaDesc","h2s","textLength","node","sessionStorage","localStorage","tsKey","ttlMs","readStorage","storage","existing","existingTs","writeStorage","value","existingSession","newId","configUrl","telemetryUrl","siteId","blackBox","runtime_default","PantheraBlackBox"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@careerdriver/black-box",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "AI Search Optimization Runtime - Automatic JSON-LD schema injection with server-side rendering support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-search",
|
|
@@ -43,14 +43,15 @@
|
|
|
43
43
|
"url": "https://github.com/careerdriver/gpto-suite/issues"
|
|
44
44
|
},
|
|
45
45
|
"homepage": "https://github.com/careerdriver/gpto-suite#readme",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup src/runtime.ts --format iife,esm --global-name PantheraBlackBox --minify --sourcemap --dts",
|
|
48
|
+
"dev": "tsup src/runtime.ts --format iife,esm --global-name PantheraBlackBox --watch",
|
|
49
|
+
"type-check": "tsc --noEmit",
|
|
50
|
+
"prepublishOnly": "pnpm build"
|
|
51
|
+
},
|
|
46
52
|
"devDependencies": {
|
|
47
53
|
"@types/node": "^25.0.10",
|
|
48
54
|
"tsup": "^8.5.1",
|
|
49
55
|
"typescript": "^5.9.3"
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "tsup src/runtime.ts --format iife,esm --global-name PantheraBlackBox --minify --sourcemap --dts",
|
|
53
|
-
"dev": "tsup src/runtime.ts --format iife,esm --global-name PantheraBlackBox --watch",
|
|
54
|
-
"type-check": "tsc --noEmit"
|
|
55
56
|
}
|
|
56
|
-
}
|
|
57
|
+
}
|