@ourroadmaps/web-sdk 1.0.0 → 1.2.0

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.
@@ -1,4 +1,4 @@
1
- "use strict";var OurRoadmaps=(()=>{var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var L=(n,e,t)=>e in n?p(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var N=(n,e)=>{for(var t in e)p(n,t,{get:e[t],enumerable:!0})},S=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of M(e))!P.call(n,o)&&o!==t&&p(n,o,{get:()=>e[o],enumerable:!(i=k(e,o))||i.enumerable});return n};var H=n=>S(p({},"__esModule",{value:!0}),n);var r=(n,e,t)=>L(n,typeof e!="symbol"?e+"":e,t);var _={};N(_,{Review:()=>v});var D={},b=(typeof D<"u","https://api.ourroadmaps.com");async function w(n){let e=await fetch(`${b}/v1/prototype-review/${n}`);if(e.status===410)throw new s("expired","This feedback session has expired");if(e.status===404)throw new s("invalid","This review link is not valid");if(!e.ok)throw new s("error","Something went wrong");return(await e.json()).data}async function x(n,e){let t=await fetch(`${b}/v1/prototype-review/${n}/comments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok)throw new s("error","Failed to submit comment");return(await t.json()).data}async function m(n){let e=await fetch(`${b}/v1/prototype-review/${n}/comments`);if(!e.ok)throw new s("error","Failed to load comments");return(await e.json()).data}var s=class extends Error{constructor(t,i){super(i);this.code=t;this.name="ReviewError"}};function E(n,e){let t=document.elementFromPoint(n,e),i=document.documentElement,o=(n+window.scrollX)/i.scrollWidth*100,a=(e+window.scrollY)/i.scrollHeight*100;if(!t||t===document.body||t===i)return{pinX:o,pinY:a,element:{selector:"body",tag:"body",text:"",ariaLabel:null,className:"",boundingBox:{x:0,y:0,w:i.scrollWidth,h:i.scrollHeight}},context:{parentTag:"",parentText:"",grandparentTag:"",siblings:[],nearbyText:""},viewportWidth:window.innerWidth,viewportHeight:window.innerHeight};let l=t.getBoundingClientRect();return{pinX:o,pinY:a,element:{selector:R(t),tag:t.tagName.toLowerCase(),text:c(t).slice(0,200),ariaLabel:t.getAttribute("aria-label"),className:t.className&&typeof t.className=="string"?t.className:"",boundingBox:{x:Math.round(l.x),y:Math.round(l.y),w:Math.round(l.width),h:Math.round(l.height)}},context:{parentTag:t.parentElement?`${t.parentElement.tagName.toLowerCase()}${y(t.parentElement)}`:"",parentText:t.parentElement?c(t.parentElement).slice(0,100):"",grandparentTag:t.parentElement?.parentElement?`${t.parentElement.parentElement.tagName.toLowerCase()}${y(t.parentElement.parentElement)}`:"",siblings:$(t),nearbyText:z(t).slice(0,200)},viewportWidth:window.innerWidth,viewportHeight:window.innerHeight}}function R(n){let e=[],t=n;for(;t&&t!==document.body;){if(t.id){e.unshift(`#${t.id}`);break}let i=t.tagName.toLowerCase();if(t.className&&typeof t.className=="string"){let o=t.className.trim().split(/\s+/).slice(0,2).join(".");o&&(i+=`.${o}`)}e.unshift(i),t=t.parentElement}return e.join(" > ")}function c(n){let e="";for(let t of n.childNodes)t.nodeType===Node.TEXT_NODE&&(e+=t.textContent?.trim()||"");return e.trim()||n.textContent?.trim().slice(0,200)||""}function y(n){if(!n.className||typeof n.className!="string")return"";let e=n.className.trim().split(/\s+/).slice(0,2).join(".");return e?`.${e}`:""}function $(n){if(!n.parentElement)return[];let e=[];for(let t of n.parentElement.children){if(t===n)continue;let i=t.tagName.toLowerCase(),o=(t.textContent?.trim()||"").slice(0,50);if(e.push(o?`${i}:${o}`:i),e.length>=4)break}return e}function z(n){let e=n.parentElement,t=0;for(;e&&t<3;){let i=c(e);if(i&&i!==c(n))return i;e=e.parentElement,t++}return""}var T=`
1
+ "use strict";var OurRoadmaps=(()=>{var h=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var z=(n,e,t)=>e in n?h(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var $=(n,e)=>{for(var t in e)h(n,t,{get:e[t],enumerable:!0})},Y=(n,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of D(e))!R.call(n,o)&&o!==t&&h(n,o,{get:()=>e[o],enumerable:!(i=H(e,o))||i.enumerable});return n};var X=n=>Y(h({},"__esModule",{value:!0}),n);var r=(n,e,t)=>z(n,typeof e!="symbol"?e+"":e,t);var j={};$(j,{Review:()=>x});var _={},g=(typeof _<"u","https://api.ourroadmaps.com");async function y(n){let e=await fetch(`${g}/v1/prototype-review/${n}`);if(e.status===410)throw new l("expired","This feedback session has expired");if(e.status===404)throw new l("invalid","This review link is not valid");if(!e.ok)throw new l("error","Something went wrong");return(await e.json()).data}async function E(n,e){let t=await fetch(`${g}/v1/prototype-review/${n}/comments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok)throw new l("error","Failed to submit comment");return(await t.json()).data}async function u(n){let e=await fetch(`${g}/v1/prototype-review/${n}/comments`);if(!e.ok)throw new l("error","Failed to load comments");return(await e.json()).data}var l=class extends Error{constructor(t,i){super(i);this.code=t;this.name="ReviewError"}};function P(n,e){let t=document.elementFromPoint(n,e),i=document.documentElement,o=(n+window.scrollX)/i.scrollWidth*100,s=(e+window.scrollY)/i.scrollHeight*100;if(!t||t===document.body||t===i)return{pinX:o,pinY:s,element:{selector:"body",tag:"body",text:"",ariaLabel:null,className:"",boundingBox:{x:0,y:0,w:i.scrollWidth,h:i.scrollHeight}},context:{parentTag:"",parentText:"",grandparentTag:"",siblings:[],nearbyText:""},viewportWidth:window.innerWidth,viewportHeight:window.innerHeight};let a=t.getBoundingClientRect();return{pinX:o,pinY:s,element:{selector:I(t),tag:t.tagName.toLowerCase(),text:f(t).slice(0,200),ariaLabel:t.getAttribute("aria-label"),className:t.className&&typeof t.className=="string"?t.className:"",boundingBox:{x:Math.round(a.x),y:Math.round(a.y),w:Math.round(a.width),h:Math.round(a.height)}},context:{parentTag:t.parentElement?`${t.parentElement.tagName.toLowerCase()}${k(t.parentElement)}`:"",parentText:t.parentElement?f(t.parentElement).slice(0,100):"",grandparentTag:t.parentElement?.parentElement?`${t.parentElement.parentElement.tagName.toLowerCase()}${k(t.parentElement.parentElement)}`:"",siblings:O(t),nearbyText:W(t).slice(0,200)},viewportWidth:window.innerWidth,viewportHeight:window.innerHeight}}function I(n){let e=[],t=n;for(;t&&t!==document.body;){if(t.id){e.unshift(`#${t.id}`);break}let i=t.tagName.toLowerCase();if(t.className&&typeof t.className=="string"){let o=t.className.trim().split(/\s+/).slice(0,2).join(".");o&&(i+=`.${o}`)}e.unshift(i),t=t.parentElement}return e.join(" > ")}function f(n){let e="";for(let t of n.childNodes)t.nodeType===Node.TEXT_NODE&&(e+=t.textContent?.trim()||"");return e.trim()||n.textContent?.trim().slice(0,200)||""}function k(n){if(!n.className||typeof n.className!="string")return"";let e=n.className.trim().split(/\s+/).slice(0,2).join(".");return e?`.${e}`:""}function O(n){if(!n.parentElement)return[];let e=[];for(let t of n.parentElement.children){if(t===n)continue;let i=t.tagName.toLowerCase(),o=(t.textContent?.trim()||"").slice(0,50);if(e.push(o?`${i}:${o}`:i),e.length>=4)break}return e}function W(n){let e=n.parentElement,t=0;for(;e&&t<3;){let i=f(e);if(i&&i!==f(n))return i;e=e.parentElement,t++}return""}var M=`
2
2
  :host {
3
3
  all: initial;
4
4
  }
@@ -257,10 +257,17 @@
257
257
  transition-duration: 0.01ms !important;
258
258
  }
259
259
  }
260
- `,C=`
260
+ `,N=`
261
+ body {
262
+ position: relative !important;
263
+ }
264
+
261
265
  .pin-container {
262
- position: fixed;
263
- inset: 0;
266
+ position: absolute;
267
+ top: 0;
268
+ left: 0;
269
+ width: 100%;
270
+ min-height: 100%;
264
271
  pointer-events: none;
265
272
  z-index: 10000;
266
273
  }
@@ -307,32 +314,28 @@
307
314
  transition-duration: 0.01ms !important;
308
315
  }
309
316
  }
310
- `;var g="roadmaps-review-pin-styles",d=class{constructor(){r(this,"container");r(this,"pins",new Map);r(this,"styleEl",null);this.container=document.createElement("div"),this.container.className="pin-container"}mount(){document.getElementById(g)||(this.styleEl=document.createElement("style"),this.styleEl.id=g,this.styleEl.textContent=C,document.head.appendChild(this.styleEl)),document.body.appendChild(this.container)}addPin(e,t,i,o){let a=document.createElement("div");a.className=o?"review-pin":"review-pin review-pin--other",a.textContent=String(e),a.style.left=`${t}%`,a.style.top=`${i}%`,a.dataset.pinNumber=String(e),this.container.appendChild(a),this.pins.set(e,a)}removePin(e){let t=this.pins.get(e);t&&(t.remove(),this.pins.delete(e))}highlightPin(e){for(let[t,i]of this.pins)i.classList.toggle("review-pin--highlighted",t===e)}clearHighlight(){for(let e of this.pins.values())e.classList.remove("review-pin--highlighted")}destroy(){this.container.remove(),this.pins.clear(),this.styleEl?(this.styleEl.remove(),this.styleEl=null):document.getElementById(g)?.remove()}};var h=class{constructor(e){this.shadowRoot=e;r(this,"card",null);r(this,"onSubmit",null);r(this,"onCancel",null)}show(e){this.hide(),this.onSubmit=e.onSubmit,this.onCancel=e.onCancel,this.card=document.createElement("div"),this.card.className="review-comment-card",this.card.style.left=`${Math.min(e.x,window.innerWidth-320)}px`,this.card.style.top=`${Math.min(e.y+20,window.innerHeight-200)}px`,this.card.innerHTML=`
317
+ `;var T="roadmaps-review-pin-styles",p=class{constructor(){r(this,"container");r(this,"pins",new Map);r(this,"styleEl",null);this.container=document.createElement("div"),this.container.className="pin-container"}mount(){document.getElementById(T)||(this.styleEl=document.createElement("style"),this.styleEl.id=T,this.styleEl.textContent=N,document.head.appendChild(this.styleEl)),document.body.appendChild(this.container)}addPin(e,t,i,o){let s=document.createElement("div");s.className=o?"review-pin":"review-pin review-pin--other",s.textContent=String(e),s.style.left=`${t}%`,s.style.top=`${i}%`,s.dataset.pinNumber=String(e),this.container.appendChild(s),this.pins.set(e,s)}removePin(e){let t=this.pins.get(e);t&&(t.remove(),this.pins.delete(e))}highlightPin(e){for(let[t,i]of this.pins)i.classList.toggle("review-pin--highlighted",t===e)}clearHighlight(){for(let e of this.pins.values())e.classList.remove("review-pin--highlighted")}destroy(){this.container.remove(),this.pins.clear(),this.styleEl?(this.styleEl.remove(),this.styleEl=null):document.getElementById(T)?.remove()}};var v=class{constructor(e){this.shadowRoot=e;r(this,"card",null);r(this,"onSubmit",null);r(this,"onCancel",null)}show(e){this.hide(),this.onSubmit=e.onSubmit,this.onCancel=e.onCancel,this.card=document.createElement("div"),this.card.className="review-comment-card",this.card.style.left=`${Math.min(e.x,window.innerWidth-320)}px`,this.card.style.top=`${Math.min(e.y+20,window.innerHeight-200)}px`,this.card.innerHTML=`
311
318
  <textarea class="review-comment-input" placeholder="Leave your feedback..." rows="3"></textarea>
312
319
  <div class="review-comment-actions">
313
320
  <button class="review-btn review-btn--cancel">Cancel</button>
314
321
  <button class="review-btn review-btn--submit">Submit</button>
315
322
  </div>
316
323
  <div class="review-comment-error" style="display:none"></div>
317
- `,this.attachListeners(),this.shadowRoot.appendChild(this.card),this.card.querySelector("textarea")?.focus()}hide(){this.card?.remove(),this.card=null}showForGeneral(e){this.show({x:window.innerWidth/2-150,y:window.innerHeight/2-100,onSubmit:e.onSubmit,onCancel:e.onCancel})}attachListeners(){this.card&&(this.card.querySelector(".review-btn--cancel")?.addEventListener("click",()=>{this.onCancel?.(),this.hide()}),this.card.querySelector(".review-btn--submit")?.addEventListener("click",()=>this.handleSubmit()),this.card.querySelector("textarea")?.addEventListener("keydown",e=>{(e.metaKey||e.ctrlKey)&&e.key==="Enter"&&(e.preventDefault(),this.handleSubmit())}))}async handleSubmit(){if(!this.card)return;let e=this.card.querySelector("textarea"),t=e.value.trim();if(!t)return;let i=this.card.querySelector(".review-btn--submit"),o=this.card.querySelector(".review-comment-error");i.disabled=!0,i.textContent="Submitting...",e.disabled=!0,o.style.display="none";try{await this.onSubmit?.(t),this.hide()}catch(a){i.disabled=!1,i.textContent="Submit",e.disabled=!1,o.textContent=a instanceof Error?a.message:"Failed to submit",o.style.display="block"}}};var u=class{constructor(e,t,i){this.token=e;this.shadowRoot=t;this.initData=i;r(this,"pinManager");r(this,"commentCard");r(this,"nextPinNumber");r(this,"pendingPinNumber",null);r(this,"promptEl",null);r(this,"toolbarEl",null);r(this,"clickHandler",null);this.pinManager=new d,this.commentCard=new h(t),this.nextPinNumber=i.nextPinNumber}async init(){document.body.style.cursor="crosshair",this.pinManager.mount(),this.showPrompt(),this.renderToolbar(),await this.loadExistingPins(),this.clickHandler=e=>this.handleClick(e),document.addEventListener("click",this.clickHandler,!0)}showPrompt(){this.promptEl=document.createElement("div"),this.promptEl.className="review-prompt",this.promptEl.innerHTML=`
324
+ `,this.attachListeners(),this.shadowRoot.appendChild(this.card),this.card.querySelector("textarea")?.focus()}hide(){this.card?.remove(),this.card=null}showForGeneral(e){this.show({x:window.innerWidth/2-150,y:window.innerHeight/2-100,onSubmit:e.onSubmit,onCancel:e.onCancel})}attachListeners(){this.card&&(this.card.querySelector(".review-btn--cancel")?.addEventListener("click",()=>{this.onCancel?.(),this.hide()}),this.card.querySelector(".review-btn--submit")?.addEventListener("click",()=>this.handleSubmit()),this.card.querySelector("textarea")?.addEventListener("keydown",e=>{(e.metaKey||e.ctrlKey)&&e.key==="Enter"&&(e.preventDefault(),this.handleSubmit())}))}async handleSubmit(){if(!this.card)return;let e=this.card.querySelector("textarea"),t=e.value.trim();if(!t)return;let i=this.card.querySelector(".review-btn--submit"),o=this.card.querySelector(".review-comment-error");i.disabled=!0,i.textContent="Submitting...",e.disabled=!0,o.style.display="none";try{await this.onSubmit?.(t),this.hide()}catch(s){i.disabled=!1,i.textContent="Submit",e.disabled=!1,o.textContent=s instanceof Error?s.message:"Failed to submit",o.style.display="block"}}};var b=class{constructor(e,t,i){this.token=e;this.shadowRoot=t;this.initData=i;r(this,"pinManager");r(this,"commentCard");r(this,"nextPinNumber");r(this,"pendingPinNumber",null);r(this,"promptEl",null);r(this,"toolbarEl",null);r(this,"clickHandler",null);this.pinManager=new p,this.commentCard=new v(t),this.nextPinNumber=i.nextPinNumber}async init(){document.body.style.cursor="crosshair",this.pinManager.mount(),this.showPrompt(),this.renderToolbar(),await this.loadExistingPins(),this.clickHandler=e=>this.handleClick(e),document.addEventListener("click",this.clickHandler,!0)}showPrompt(){this.promptEl=document.createElement("div"),this.promptEl.className="review-prompt",this.promptEl.innerHTML=`
318
325
  <h3 style="margin:0 0 8px;font-size:16px;">Click anywhere to leave feedback</h3>
319
326
  <p style="margin:0;color:#666;font-size:14px;">Drop numbered pins on elements you want to comment on</p>
320
327
  `,this.shadowRoot.appendChild(this.promptEl),setTimeout(()=>this.dismissPrompt(),5e3)}dismissPrompt(){this.promptEl&&(this.promptEl.remove(),this.promptEl=null)}renderToolbar(){this.toolbarEl=document.createElement("div"),this.toolbarEl.className="review-toolbar",this.updateToolbar(),this.shadowRoot.appendChild(this.toolbarEl)}updateToolbar(){if(!this.toolbarEl)return;let e=this.nextPinNumber-1;this.toolbarEl.innerHTML=`
321
328
  <span style="font-weight:500;">${this.initData.session.name}</span>
322
329
  <span style="opacity:0.7;">${e} pin${e!==1?"s":""}</span>
323
330
  <button class="review-btn review-btn--submit" style="margin-left:auto;padding:6px 12px;font-size:13px;">General Comment</button>
324
- `,this.toolbarEl.querySelector("button")?.addEventListener("click",t=>{t.stopPropagation(),this.handleGeneralComment()})}async loadExistingPins(){try{let e=await m(this.token);for(let t of e)if(t.pinNumber!=null&&t.pinData){let i=t.commentText!=null;this.pinManager.addPin(t.pinNumber,t.pinData.pinX,t.pinData.pinY,i)}}catch{}}handleClick(e){if(e.composedPath().some(a=>a instanceof HTMLElement&&a.closest?.("#ourroadmaps-review"))||this.pendingPinNumber!=null)return;this.dismissPrompt();let i=E(e.clientX,e.clientY),o=this.nextPinNumber;this.pinManager.addPin(o,i.pinX,i.pinY,!0),this.pendingPinNumber=o,this.commentCard.show({x:e.clientX,y:e.clientY,onSubmit:async a=>{await x(this.token,{commentText:a,pinNumber:o,pinData:i,pageUrl:window.location.pathname}),this.pendingPinNumber=null,this.nextPinNumber++,this.updateToolbar(),this.showToast("Comment saved")},onCancel:()=>{this.pinManager.removePin(o),this.pendingPinNumber=null}}),e.preventDefault(),e.stopPropagation()}handleGeneralComment(){this.pendingPinNumber==null&&this.commentCard.showForGeneral({onSubmit:async e=>{await x(this.token,{commentText:e,pinNumber:null,pinData:null,pageUrl:window.location.pathname}),this.showToast("Comment saved")},onCancel:()=>{}})}showToast(e){let t=document.createElement("div");t.className="review-toast",t.textContent=e,this.shadowRoot.appendChild(t),setTimeout(()=>t.remove(),2500)}destroy(){document.body.style.cursor="",this.clickHandler&&document.removeEventListener("click",this.clickHandler,!0),this.dismissPrompt(),this.toolbarEl?.remove(),this.commentCard.hide(),this.pinManager.destroy()}};var f=class{constructor(e,t){this.token=e;this.shadowRoot=t;r(this,"pinManager");r(this,"toolbarEl",null);r(this,"tooltipEl",null);r(this,"comments",[]);r(this,"pinClickHandler",null);this.pinManager=new d}async init(){this.pinManager.mount();try{this.comments=await m(this.token);for(let e of this.comments)e.pinNumber!=null&&e.pinData&&this.pinManager.addPin(e.pinNumber,e.pinData.pinX,e.pinData.pinY,!0)}catch{}this.renderToolbar(),this.pinClickHandler=e=>{let t=e.target;if(t.classList?.contains("review-pin")){let i=Number(t.dataset.pinNumber);i&&this.handlePinClick(i,e)}},document.addEventListener("click",this.pinClickHandler,!0)}renderToolbar(){this.toolbarEl=document.createElement("div"),this.toolbarEl.className="review-toolbar";let e=this.comments.filter(t=>t.pinNumber!=null).length;this.toolbarEl.innerHTML=`
331
+ `,this.toolbarEl.querySelector("button")?.addEventListener("click",t=>{t.stopPropagation(),this.handleGeneralComment()})}async loadExistingPins(){try{let e=await u(this.token);for(let t of e)if(t.pinNumber!=null&&t.pinData){let i=t.commentText!=null;this.pinManager.addPin(t.pinNumber,t.pinData.pinX,t.pinData.pinY,i)}}catch{}}handleClick(e){if(e.composedPath().some(s=>s instanceof HTMLElement&&s.closest?.("#ourroadmaps-review"))||this.pendingPinNumber!=null)return;this.dismissPrompt();let i=P(e.clientX,e.clientY),o=this.nextPinNumber;this.pinManager.addPin(o,i.pinX,i.pinY,!0),this.pendingPinNumber=o,this.commentCard.show({x:e.clientX,y:e.clientY,onSubmit:async s=>{await E(this.token,{commentText:s,pinNumber:o,pinData:i,pageUrl:window.location.pathname}),this.pendingPinNumber=null,this.nextPinNumber++,this.updateToolbar(),this.showToast("Comment saved")},onCancel:()=>{this.pinManager.removePin(o),this.pendingPinNumber=null}}),e.preventDefault(),e.stopPropagation()}handleGeneralComment(){this.pendingPinNumber==null&&this.commentCard.showForGeneral({onSubmit:async e=>{await E(this.token,{commentText:e,pinNumber:null,pinData:null,pageUrl:window.location.pathname}),this.showToast("Comment saved")},onCancel:()=>{}})}showToast(e){let t=document.createElement("div");t.className="review-toast",t.textContent=e,this.shadowRoot.appendChild(t),setTimeout(()=>t.remove(),2500)}destroy(){document.body.style.cursor="",this.clickHandler&&document.removeEventListener("click",this.clickHandler,!0),this.dismissPrompt(),this.toolbarEl?.remove(),this.commentCard.hide(),this.pinManager.destroy()}};var w=class{constructor(e,t){this.token=e;this.shadowRoot=t;r(this,"pinManager");r(this,"toolbarEl",null);r(this,"tooltipEl",null);r(this,"comments",[]);r(this,"pinClickHandler",null);this.pinManager=new p}async init(){this.pinManager.mount();try{this.comments=await u(this.token);for(let e of this.comments)e.pinNumber!=null&&e.pinData&&this.pinManager.addPin(e.pinNumber,e.pinData.pinX,e.pinData.pinY,!0)}catch{}this.renderToolbar(),this.pinClickHandler=e=>{let t=e.target;if(t.classList?.contains("review-pin")){let i=Number(t.dataset.pinNumber);i&&this.handlePinClick(i,e)}},document.addEventListener("click",this.pinClickHandler,!0),this.autoFocusPin()}renderToolbar(){this.toolbarEl=document.createElement("div"),this.toolbarEl.className="review-toolbar";let e=this.comments.filter(t=>t.pinNumber!=null).length;this.toolbarEl.innerHTML=`
325
332
  <span style="font-weight:500;">Triage Mode</span>
326
333
  <span style="opacity:0.7;">${e} pin${e!==1?"s":""}</span>
327
- `,this.shadowRoot.appendChild(this.toolbarEl)}handlePinClick(e,t){this.hideTooltip(),this.pinManager.highlightPin(e);let i=this.comments.find(l=>l.pinNumber===e);if(!i)return;this.tooltipEl=document.createElement("div"),this.tooltipEl.className="review-tooltip";let o=new Date(i.createdAt).toLocaleString();this.tooltipEl.innerHTML=`
328
- <div style="font-weight:500;margin-bottom:4px;">Pin #${e}</div>
329
- <div style="margin-bottom:4px;">${i.commentText||"(no text)"}</div>
330
- <div style="font-size:11px;opacity:0.7;">${o}</div>
331
- `,this.tooltipEl.style.position="fixed",this.tooltipEl.style.left=`${Math.min(t.clientX+16,window.innerWidth-300)}px`,this.tooltipEl.style.top=`${Math.min(t.clientY-10,window.innerHeight-150)}px`,this.shadowRoot.appendChild(this.tooltipEl);let a=l=>{l.target!==this.tooltipEl&&!this.tooltipEl?.contains(l.target)&&(this.hideTooltip(),this.pinManager.clearHighlight(),document.removeEventListener("click",a,!0))};setTimeout(()=>document.addEventListener("click",a,!0),0)}hideTooltip(){this.tooltipEl?.remove(),this.tooltipEl=null}destroy(){this.pinClickHandler&&document.removeEventListener("click",this.pinClickHandler,!0),this.hideTooltip(),this.toolbarEl?.remove(),this.pinManager.destroy()}};var v=class{constructor(e={}){r(this,"root");r(this,"shadow");r(this,"mode",null);r(this,"_isDestroyed",!1);this.root=document.createElement("div"),this.root.id="ourroadmaps-review",this.shadow=this.root.attachShadow({mode:"open"});let t=document.createElement("style");t.textContent=T,this.shadow.appendChild(t),document.body.appendChild(this.root)}async init(){let e=new URLSearchParams(window.location.search),t=e.get("review"),i=e.get("triage");if(t)try{let o=await w(t);this.mode=new u(t,this.shadow,o),await this.mode.init()}catch(o){o instanceof s&&this.showErrorOverlay(o.code==="expired"?"This feedback session has expired":"This review link is no longer valid")}else if(i)try{await w(i),this.mode=new f(i,this.shadow),await this.mode.init()}catch(o){o instanceof s&&this.showErrorOverlay(o.code==="expired"?"This feedback session has expired":"This review link is no longer valid")}}showErrorOverlay(e){let t=document.createElement("div");t.className="review-expired-overlay",t.innerHTML=`
334
+ `,this.shadowRoot.appendChild(this.toolbarEl)}handlePinClick(e,t){this.hideTooltip(),this.pinManager.highlightPin(e),this.showTooltipForPin(e,t.clientX+16,t.clientY-10)}showTooltipForPin(e,t,i){this.hideTooltip();let o=this.comments.find(d=>d.pinNumber===e);if(!o)return;this.tooltipEl=document.createElement("div"),this.tooltipEl.className="review-tooltip";let s=new Date(o.createdAt).toLocaleString(),a=document.createElement("div");if(a.style.cssText="font-weight:500;margin-bottom:4px;",a.textContent=`Pin #${e}`,this.tooltipEl.appendChild(a),o.reviewerName){let d=document.createElement("div");d.style.cssText="font-size:11px;opacity:0.7;margin-bottom:2px;",d.textContent=o.reviewerName,this.tooltipEl.appendChild(d)}let m=document.createElement("div");m.style.cssText="margin-bottom:4px;",m.textContent=o.commentText||"(no text)",this.tooltipEl.appendChild(m);let c=document.createElement("div");c.style.cssText="font-size:11px;opacity:0.7;",c.textContent=s,this.tooltipEl.appendChild(c);let L=t??window.innerWidth/2,S=i??window.innerHeight/3;this.tooltipEl.style.position="fixed",this.tooltipEl.style.left=`${Math.min(L,window.innerWidth-300)}px`,this.tooltipEl.style.top=`${Math.min(S,window.innerHeight-150)}px`,this.shadowRoot.appendChild(this.tooltipEl);let C=d=>{d.target!==this.tooltipEl&&!this.tooltipEl?.contains(d.target)&&(this.hideTooltip(),this.pinManager.clearHighlight(),document.removeEventListener("click",C,!0))};setTimeout(()=>document.addEventListener("click",C,!0),0)}autoFocusPin(){let t=new URLSearchParams(window.location.search).get("pin");if(!t)return;let i=Number(t);if(!i)return;let o=this.comments.find(c=>c.pinNumber===i);if(!o?.pinData)return;let s=document.documentElement,a=o.pinData.pinX/100*s.scrollWidth,m=o.pinData.pinY/100*s.scrollHeight;window.scrollTo({left:a-window.innerWidth/2,top:m-window.innerHeight/2,behavior:"smooth"}),setTimeout(()=>{this.pinManager.highlightPin(i),this.showTooltipForPin(i)},500)}hideTooltip(){this.tooltipEl?.remove(),this.tooltipEl=null}destroy(){this.pinClickHandler&&document.removeEventListener("click",this.pinClickHandler,!0),this.hideTooltip(),this.toolbarEl?.remove(),this.pinManager.destroy()}};var x=class{constructor(e={}){r(this,"root");r(this,"shadow");r(this,"mode",null);r(this,"_isDestroyed",!1);this.root=document.createElement("div"),this.root.id="ourroadmaps-review",this.shadow=this.root.attachShadow({mode:"open"});let t=document.createElement("style");t.textContent=M,this.shadow.appendChild(t),document.body.appendChild(this.root)}async init(){let e=new URLSearchParams(window.location.search),t=e.get("review"),i=e.get("triage");if(t)try{let o=await y(t);this.mode=new b(t,this.shadow,o),await this.mode.init()}catch(o){o instanceof l&&this.showErrorOverlay(o.code==="expired"?"This feedback session has expired":"This review link is no longer valid")}else if(i)try{await y(i),this.mode=new w(i,this.shadow),await this.mode.init()}catch(o){o instanceof l&&this.showErrorOverlay(o.code==="expired"?"This feedback session has expired":"This review link is no longer valid")}}showErrorOverlay(e){let t=document.createElement("div");t.className="review-expired-overlay",t.innerHTML=`
332
335
  <div class="review-expired-card">
333
336
  <h2 style="margin:0 0 8px;font-size:18px;">Session Unavailable</h2>
334
337
  <p style="margin:0;color:#666;font-size:14px;">${e}</p>
335
338
  <p style="margin:12px 0 0;color:#999;font-size:13px;">Contact the prototype owner for a new link.</p>
336
339
  </div>
337
- `,this.shadow.appendChild(t),document.body.style.filter="grayscale(0.8)"}destroy(){this._isDestroyed||(this._isDestroyed=!0,this.mode?.destroy(),this.root.remove(),document.body.style.filter="")}};return H(_);})();
340
+ `,this.shadow.appendChild(t),document.body.style.filter="grayscale(0.8)"}destroy(){this._isDestroyed||(this._isDestroyed=!0,this.mode?.destroy(),this.root.remove(),document.body.style.filter="")}};return X(j);})();
338
341
  //# sourceMappingURL=review.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/review/index.ts","../src/review/api.ts","../src/review/ElementCapture.ts","../src/review/styles.ts","../src/review/PinManager.ts","../src/review/CommentCard.ts","../src/review/ReviewMode.ts","../src/review/TriageMode.ts","../src/review/Review.ts"],"sourcesContent":["export { Review } from './Review'\nexport type { PinData, ReviewOptions } from './types'\n","import type { PinData, ReviewComment, ReviewInitData } from './types'\n\n// Default to production, override with env var for local dev\nconst API_URL = (() => {\n // Vite dev mode\n if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_URL) {\n return import.meta.env.VITE_API_URL\n }\n return 'https://api.ourroadmaps.com'\n})()\n\nexport async function validateToken(token: string): Promise<ReviewInitData> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}`)\n if (res.status === 410) throw new ReviewError('expired', 'This feedback session has expired')\n if (res.status === 404) throw new ReviewError('invalid', 'This review link is not valid')\n if (!res.ok) throw new ReviewError('error', 'Something went wrong')\n const body = await res.json()\n return body.data\n}\n\n// TODO: Wire into SDK — show name prompt on first visit, call identify() to transition invite status from 'pending' to 'opened'\nexport async function identify(token: string, name: string): Promise<void> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/identify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to identify')\n}\n\nexport async function submitComment(\n token: string,\n comment: { commentText: string; pinNumber: number | null; pinData: PinData | null; pageUrl: string | null },\n): Promise<{ id: string; pinNumber: number | null; createdAt: string }> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(comment),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to submit comment')\n const body = await res.json()\n return body.data\n}\n\nexport async function fetchComments(token: string): Promise<ReviewComment[]> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`)\n if (!res.ok) throw new ReviewError('error', 'Failed to load comments')\n const body = await res.json()\n return body.data\n}\n\nexport class ReviewError extends Error {\n constructor(\n public code: 'expired' | 'invalid' | 'error',\n message: string,\n ) {\n super(message)\n this.name = 'ReviewError'\n }\n}\n","import type { PinData } from './types'\n\nexport function captureElementContext(clientX: number, clientY: number): PinData {\n const el = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n const docEl = document.documentElement\n\n const pinX = ((clientX + window.scrollX) / docEl.scrollWidth) * 100\n const pinY = ((clientY + window.scrollY) / docEl.scrollHeight) * 100\n\n if (!el || el === document.body || el === docEl) {\n return {\n pinX,\n pinY,\n element: {\n selector: 'body',\n tag: 'body',\n text: '',\n ariaLabel: null,\n className: '',\n boundingBox: { x: 0, y: 0, w: docEl.scrollWidth, h: docEl.scrollHeight },\n },\n context: {\n parentTag: '',\n parentText: '',\n grandparentTag: '',\n siblings: [],\n nearbyText: '',\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n const rect = el.getBoundingClientRect()\n\n return {\n pinX,\n pinY,\n element: {\n selector: buildSelector(el),\n tag: el.tagName.toLowerCase(),\n text: getDirectText(el).slice(0, 200),\n ariaLabel: el.getAttribute('aria-label'),\n className: el.className && typeof el.className === 'string' ? el.className : '',\n boundingBox: {\n x: Math.round(rect.x),\n y: Math.round(rect.y),\n w: Math.round(rect.width),\n h: Math.round(rect.height),\n },\n },\n context: {\n parentTag: el.parentElement ? `${el.parentElement.tagName.toLowerCase()}${classStr(el.parentElement)}` : '',\n parentText: el.parentElement ? getDirectText(el.parentElement).slice(0, 100) : '',\n grandparentTag: el.parentElement?.parentElement\n ? `${el.parentElement.parentElement.tagName.toLowerCase()}${classStr(el.parentElement.parentElement)}`\n : '',\n siblings: getSiblingsSummary(el),\n nearbyText: getNearbyText(el).slice(0, 200),\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\nfunction buildSelector(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(`#${current.id}`)\n break\n }\n\n let part = current.tagName.toLowerCase()\n if (current.className && typeof current.className === 'string') {\n const classes = current.className.trim().split(/\\s+/).slice(0, 2).join('.')\n if (classes) part += `.${classes}`\n }\n\n parts.unshift(part)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getDirectText(el: HTMLElement): string {\n let text = ''\n for (const node of el.childNodes) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent?.trim() || ''\n }\n }\n return text.trim() || el.textContent?.trim().slice(0, 200) || ''\n}\n\nfunction classStr(el: HTMLElement): string {\n if (!el.className || typeof el.className !== 'string') return ''\n const cls = el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n return cls ? `.${cls}` : ''\n}\n\nfunction getSiblingsSummary(el: HTMLElement): string[] {\n if (!el.parentElement) return []\n const siblings: string[] = []\n for (const child of el.parentElement.children) {\n if (child === el) continue\n const tag = child.tagName.toLowerCase()\n const text = (child.textContent?.trim() || '').slice(0, 50)\n siblings.push(text ? `${tag}:${text}` : tag)\n if (siblings.length >= 4) break\n }\n return siblings\n}\n\nfunction getNearbyText(el: HTMLElement): string {\n // Walk up to find the nearest container with meaningful text\n let current: HTMLElement | null = el.parentElement\n let depth = 0\n while (current && depth < 3) {\n const text = getDirectText(current)\n if (text && text !== getDirectText(el)) return text\n current = current.parentElement\n depth++\n }\n return ''\n}\n","export const REVIEW_STYLES = `\n:host {\n all: initial;\n}\n\n/* ─── Pin Container (rendered outside shadow DOM) ─── */\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n/* ─── Pin Marker ─── */\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n/* ─── Toolbar ─── */\n.review-toolbar {\n position: fixed;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n border-radius: 12px 12px 0 0;\n padding: 10px 20px;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n z-index: 10001;\n backdrop-filter: blur(8px);\n box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);\n}\n\n/* ─── Comment Card ─── */\n.review-comment-card {\n position: fixed;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);\n width: 300px;\n padding: 16px;\n z-index: 10002;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Comment Textarea ─── */\n.review-comment-input {\n width: 100%;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n padding: 10px 12px;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n color: #1f2937;\n background: #fafafa;\n box-sizing: border-box;\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.review-comment-input:focus {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);\n background: #fff;\n}\n\n.review-comment-input::placeholder {\n color: #9ca3af;\n}\n\n.review-comment-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ─── Comment Actions ─── */\n.review-comment-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n}\n\n/* ─── Buttons ─── */\n.review-btn {\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n border: none;\n transition: background 0.15s ease, opacity 0.15s ease;\n}\n\n.review-btn--cancel {\n color: #6b7280;\n background: transparent;\n}\n\n.review-btn--cancel:hover {\n background: #f3f4f6;\n}\n\n.review-btn--submit {\n background: #7c3aed;\n color: #fff;\n}\n\n.review-btn--submit:hover {\n background: #6d28d9;\n}\n\n.review-btn--submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ─── Comment Error ─── */\n.review-comment-error {\n color: #dc2626;\n font-size: 12px;\n margin-top: 8px;\n font-family: system-ui, -apple-system, sans-serif;\n}\n\n/* ─── Name Prompt ─── */\n.review-prompt {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: #fff;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.08);\n border-radius: 12px;\n padding: 28px 32px;\n z-index: 10002;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.06);\n max-width: 360px;\n width: 90%;\n}\n\n/* ─── Expired Overlay ─── */\n.review-expired-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10003;\n backdrop-filter: blur(2px);\n}\n\n.review-expired-card {\n background: #fff;\n border-radius: 12px;\n padding: 28px 32px;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(0, 0, 0, 0.06);\n}\n\n/* ─── Toast ─── */\n.review-toast {\n position: fixed;\n bottom: 80px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n padding: 10px 20px;\n border-radius: 8px;\n z-index: 10003;\n pointer-events: none;\n animation: review-toast-fade 2.5s ease forwards;\n backdrop-filter: blur(8px);\n}\n\n@keyframes review-toast-fade {\n 0% { opacity: 0; transform: translateX(-50%) translateY(8px); }\n 10% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-4px); }\n}\n\n/* ─── Tooltip ─── */\n.review-tooltip {\n position: absolute;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n border-radius: 6px;\n padding: 8px 12px;\n max-width: 280px;\n z-index: 10002;\n pointer-events: none;\n backdrop-filter: blur(8px);\n}\n\n/* ─── Reduced Motion ─── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n`\n\n/**\n * Subset of styles for pins that render outside the shadow DOM.\n * Injected into document.head by PinManager.\n */\nexport const PIN_DOCUMENT_STYLES = `\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .review-pin, .review-pin:hover, .review-pin--highlighted {\n transition-duration: 0.01ms !important;\n }\n}\n`\n","import { PIN_DOCUMENT_STYLES } from './styles'\n\nconst STYLE_ID = 'roadmaps-review-pin-styles'\n\nexport class PinManager {\n private container: HTMLDivElement\n private pins: Map<number, HTMLElement> = new Map()\n private styleEl: HTMLStyleElement | null = null\n\n constructor() {\n this.container = document.createElement('div')\n this.container.className = 'pin-container'\n }\n\n mount(): void {\n // Inject pin styles into document.head (pins live outside shadow DOM)\n if (!document.getElementById(STYLE_ID)) {\n this.styleEl = document.createElement('style')\n this.styleEl.id = STYLE_ID\n this.styleEl.textContent = PIN_DOCUMENT_STYLES\n document.head.appendChild(this.styleEl)\n }\n\n document.body.appendChild(this.container)\n }\n\n addPin(pinNumber: number, x: number, y: number, isMine: boolean): void {\n const pin = document.createElement('div')\n pin.className = isMine ? 'review-pin' : 'review-pin review-pin--other'\n pin.textContent = String(pinNumber)\n pin.style.left = `${x}%`\n pin.style.top = `${y}%`\n pin.dataset.pinNumber = String(pinNumber)\n this.container.appendChild(pin)\n this.pins.set(pinNumber, pin)\n }\n\n removePin(pinNumber: number): void {\n const pin = this.pins.get(pinNumber)\n if (pin) {\n pin.remove()\n this.pins.delete(pinNumber)\n }\n }\n\n highlightPin(pinNumber: number): void {\n for (const [num, el] of this.pins) {\n el.classList.toggle('review-pin--highlighted', num === pinNumber)\n }\n }\n\n clearHighlight(): void {\n for (const el of this.pins.values()) {\n el.classList.remove('review-pin--highlighted')\n }\n }\n\n destroy(): void {\n this.container.remove()\n this.pins.clear()\n\n // Clean up injected style element\n if (this.styleEl) {\n this.styleEl.remove()\n this.styleEl = null\n } else {\n document.getElementById(STYLE_ID)?.remove()\n }\n }\n}\n","export class CommentCard {\n private card: HTMLElement | null = null\n private onSubmit: ((text: string) => Promise<void>) | null = null\n private onCancel: (() => void) | null = null\n\n constructor(private shadowRoot: ShadowRoot) {}\n\n show(options: {\n x: number\n y: number\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n this.hide()\n this.onSubmit = options.onSubmit\n this.onCancel = options.onCancel\n\n this.card = document.createElement('div')\n this.card.className = 'review-comment-card'\n // Position near click point, adjust to stay in viewport\n this.card.style.left = `${Math.min(options.x, window.innerWidth - 320)}px`\n this.card.style.top = `${Math.min(options.y + 20, window.innerHeight - 200)}px`\n\n this.card.innerHTML = `\n <textarea class=\"review-comment-input\" placeholder=\"Leave your feedback...\" rows=\"3\"></textarea>\n <div class=\"review-comment-actions\">\n <button class=\"review-btn review-btn--cancel\">Cancel</button>\n <button class=\"review-btn review-btn--submit\">Submit</button>\n </div>\n <div class=\"review-comment-error\" style=\"display:none\"></div>\n `\n\n this.attachListeners()\n this.shadowRoot.appendChild(this.card)\n\n const textarea = this.card.querySelector('textarea')\n textarea?.focus()\n }\n\n hide(): void {\n this.card?.remove()\n this.card = null\n }\n\n showForGeneral(options: {\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n // Show centered for general (non-pinned) comments\n this.show({\n x: window.innerWidth / 2 - 150,\n y: window.innerHeight / 2 - 100,\n onSubmit: options.onSubmit,\n onCancel: options.onCancel,\n })\n }\n\n private attachListeners(): void {\n if (!this.card) return\n\n this.card.querySelector('.review-btn--cancel')?.addEventListener('click', () => {\n this.onCancel?.()\n this.hide()\n })\n\n this.card.querySelector('.review-btn--submit')?.addEventListener('click', () => this.handleSubmit())\n\n this.card.querySelector('textarea')?.addEventListener('keydown', (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n private async handleSubmit(): Promise<void> {\n if (!this.card) return\n const textarea = this.card.querySelector('textarea') as HTMLTextAreaElement\n const text = textarea.value.trim()\n if (!text) return\n\n const submitBtn = this.card.querySelector('.review-btn--submit') as HTMLButtonElement\n const errorEl = this.card.querySelector('.review-comment-error') as HTMLElement\n submitBtn.disabled = true\n submitBtn.textContent = 'Submitting...'\n textarea.disabled = true\n errorEl.style.display = 'none'\n\n try {\n await this.onSubmit?.(text)\n this.hide()\n } catch (err) {\n submitBtn.disabled = false\n submitBtn.textContent = 'Submit'\n textarea.disabled = false\n errorEl.textContent = err instanceof Error ? err.message : 'Failed to submit'\n errorEl.style.display = 'block'\n }\n }\n}\n","import { submitComment, fetchComments } from './api'\nimport { captureElementContext } from './ElementCapture'\nimport { PinManager } from './PinManager'\nimport { CommentCard } from './CommentCard'\nimport type { ReviewInitData } from './types'\n\nexport class ReviewMode {\n private pinManager: PinManager\n private commentCard: CommentCard\n private nextPinNumber: number\n private pendingPinNumber: number | null = null\n private promptEl: HTMLElement | null = null\n private toolbarEl: HTMLElement | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n private initData: ReviewInitData,\n ) {\n this.pinManager = new PinManager()\n this.commentCard = new CommentCard(shadowRoot)\n this.nextPinNumber = initData.nextPinNumber\n }\n\n async init(): Promise<void> {\n // Set crosshair cursor on body\n document.body.style.cursor = 'crosshair'\n\n // Mount pin manager\n this.pinManager.mount()\n\n // Render first-visit prompt\n this.showPrompt()\n\n // Render bottom toolbar\n this.renderToolbar()\n\n // Fetch and render existing pins\n await this.loadExistingPins()\n\n // Listen for clicks to drop pins\n this.clickHandler = (e: MouseEvent) => this.handleClick(e)\n document.addEventListener('click', this.clickHandler, true)\n }\n\n private showPrompt(): void {\n this.promptEl = document.createElement('div')\n this.promptEl.className = 'review-prompt'\n this.promptEl.innerHTML = `\n <h3 style=\"margin:0 0 8px;font-size:16px;\">Click anywhere to leave feedback</h3>\n <p style=\"margin:0;color:#666;font-size:14px;\">Drop numbered pins on elements you want to comment on</p>\n `\n this.shadowRoot.appendChild(this.promptEl)\n\n // Auto-dismiss after 5 seconds\n setTimeout(() => this.dismissPrompt(), 5000)\n }\n\n private dismissPrompt(): void {\n if (this.promptEl) {\n this.promptEl.remove()\n this.promptEl = null\n }\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n this.updateToolbar()\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private updateToolbar(): void {\n if (!this.toolbarEl) return\n const pinCount = this.nextPinNumber - 1\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">${this.initData.session.name}</span>\n <span style=\"opacity:0.7;\">${pinCount} pin${pinCount !== 1 ? 's' : ''}</span>\n <button class=\"review-btn review-btn--submit\" style=\"margin-left:auto;padding:6px 12px;font-size:13px;\">General Comment</button>\n `\n this.toolbarEl.querySelector('button')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleGeneralComment()\n })\n }\n\n private async loadExistingPins(): Promise<void> {\n try {\n const comments = await fetchComments(this.token)\n for (const c of comments) {\n if (c.pinNumber != null && c.pinData) {\n // commentText being non-null means it's the current reviewer's pin\n const isMine = c.commentText != null\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine)\n }\n }\n } catch {\n // Silently fail - pins just won't show up\n }\n }\n\n private handleClick(e: MouseEvent): void {\n // Don't intercept clicks on our own shadow DOM elements\n const path = e.composedPath()\n if (path.some((el) => el instanceof HTMLElement && el.closest?.('#ourroadmaps-review'))) return\n\n // Don't intercept if comment card is open\n if (this.pendingPinNumber != null) return\n\n // Dismiss first-visit prompt on first click\n this.dismissPrompt()\n\n const pinData = captureElementContext(e.clientX, e.clientY)\n const pinNumber = this.nextPinNumber\n\n // Add pin at click position\n this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true)\n this.pendingPinNumber = pinNumber\n\n // Show comment card\n this.commentCard.show({\n x: e.clientX,\n y: e.clientY,\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber,\n pinData,\n pageUrl: window.location.pathname,\n })\n this.pendingPinNumber = null\n this.nextPinNumber++\n this.updateToolbar()\n this.showToast('Comment saved')\n },\n onCancel: () => {\n // Remove the pending pin\n this.pinManager.removePin(pinNumber)\n this.pendingPinNumber = null\n },\n })\n\n e.preventDefault()\n e.stopPropagation()\n }\n\n private handleGeneralComment(): void {\n if (this.pendingPinNumber != null) return\n\n this.commentCard.showForGeneral({\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber: null,\n pinData: null,\n pageUrl: window.location.pathname,\n })\n this.showToast('Comment saved')\n },\n onCancel: () => {},\n })\n }\n\n private showToast(message: string): void {\n const toast = document.createElement('div')\n toast.className = 'review-toast'\n toast.textContent = message\n this.shadowRoot.appendChild(toast)\n setTimeout(() => toast.remove(), 2500)\n }\n\n destroy(): void {\n document.body.style.cursor = ''\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n }\n this.dismissPrompt()\n this.toolbarEl?.remove()\n this.commentCard.hide()\n this.pinManager.destroy()\n }\n}\n","import { fetchComments } from './api'\nimport { PinManager } from './PinManager'\nimport type { ReviewComment } from './types'\n\nexport class TriageMode {\n private pinManager: PinManager\n private toolbarEl: HTMLElement | null = null\n private tooltipEl: HTMLElement | null = null\n private comments: ReviewComment[] = []\n private pinClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n ) {\n this.pinManager = new PinManager()\n }\n\n async init(): Promise<void> {\n this.pinManager.mount()\n\n // Fetch all comments\n try {\n this.comments = await fetchComments(this.token)\n for (const c of this.comments) {\n if (c.pinNumber != null && c.pinData) {\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true)\n }\n }\n } catch {\n // Show error state\n }\n\n this.renderToolbar()\n\n // Listen for clicks on pins\n this.pinClickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.classList?.contains('review-pin')) {\n const num = Number(target.dataset.pinNumber)\n if (num) this.handlePinClick(num, e)\n }\n }\n document.addEventListener('click', this.pinClickHandler, true)\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n const count = this.comments.filter((c) => c.pinNumber != null).length\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">Triage Mode</span>\n <span style=\"opacity:0.7;\">${count} pin${count !== 1 ? 's' : ''}</span>\n `\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private handlePinClick(pinNumber: number, e: MouseEvent): void {\n this.hideTooltip()\n this.pinManager.highlightPin(pinNumber)\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment) return\n\n this.tooltipEl = document.createElement('div')\n this.tooltipEl.className = 'review-tooltip'\n\n const time = new Date(comment.createdAt).toLocaleString()\n this.tooltipEl.innerHTML = `\n <div style=\"font-weight:500;margin-bottom:4px;\">Pin #${pinNumber}</div>\n <div style=\"margin-bottom:4px;\">${comment.commentText || '(no text)'}</div>\n <div style=\"font-size:11px;opacity:0.7;\">${time}</div>\n `\n\n // Position near pin\n this.tooltipEl.style.position = 'fixed'\n this.tooltipEl.style.left = `${Math.min(e.clientX + 16, window.innerWidth - 300)}px`\n this.tooltipEl.style.top = `${Math.min(e.clientY - 10, window.innerHeight - 150)}px`\n this.shadowRoot.appendChild(this.tooltipEl)\n\n // Click elsewhere to dismiss\n const dismiss = (ev: MouseEvent) => {\n if (ev.target !== this.tooltipEl && !this.tooltipEl?.contains(ev.target as Node)) {\n this.hideTooltip()\n this.pinManager.clearHighlight()\n document.removeEventListener('click', dismiss, true)\n }\n }\n setTimeout(() => document.addEventListener('click', dismiss, true), 0)\n }\n\n private hideTooltip(): void {\n this.tooltipEl?.remove()\n this.tooltipEl = null\n }\n\n destroy(): void {\n if (this.pinClickHandler) {\n document.removeEventListener('click', this.pinClickHandler, true)\n }\n this.hideTooltip()\n this.toolbarEl?.remove()\n this.pinManager.destroy()\n }\n}\n","import { validateToken, ReviewError } from './api'\nimport { ReviewMode } from './ReviewMode'\nimport { TriageMode } from './TriageMode'\nimport { REVIEW_STYLES } from './styles'\nimport type { ReviewOptions } from './types'\n\nexport class Review {\n private root: HTMLElement\n private shadow: ShadowRoot\n private mode: ReviewMode | TriageMode | null = null\n private _isDestroyed = false\n\n constructor(_options: ReviewOptions = {}) {\n this.root = document.createElement('div')\n this.root.id = 'ourroadmaps-review'\n this.shadow = this.root.attachShadow({ mode: 'open' })\n\n const styleEl = document.createElement('style')\n styleEl.textContent = REVIEW_STYLES\n this.shadow.appendChild(styleEl)\n\n document.body.appendChild(this.root)\n }\n\n async init(): Promise<void> {\n const params = new URLSearchParams(window.location.search)\n const reviewToken = params.get('review')\n const triageToken = params.get('triage')\n\n if (reviewToken) {\n try {\n const data = await validateToken(reviewToken)\n this.mode = new ReviewMode(reviewToken, this.shadow, data)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n } else if (triageToken) {\n try {\n await validateToken(triageToken)\n this.mode = new TriageMode(triageToken, this.shadow)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n }\n }\n\n private showErrorOverlay(message: string): void {\n const overlay = document.createElement('div')\n overlay.className = 'review-expired-overlay'\n overlay.innerHTML = `\n <div class=\"review-expired-card\">\n <h2 style=\"margin:0 0 8px;font-size:18px;\">Session Unavailable</h2>\n <p style=\"margin:0;color:#666;font-size:14px;\">${message}</p>\n <p style=\"margin:12px 0 0;color:#999;font-size:13px;\">Contact the prototype owner for a new link.</p>\n </div>\n `\n this.shadow.appendChild(overlay)\n\n // Apply grayscale to body\n document.body.style.filter = 'grayscale(0.8)'\n }\n\n destroy(): void {\n if (this._isDestroyed) return\n this._isDestroyed = true\n this.mode?.destroy()\n this.root.remove()\n document.body.style.filter = ''\n }\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["review_exports","__export","Review","import_meta","API_URL","validateToken","token","res","ReviewError","submitComment","token","comment","res","API_URL","ReviewError","fetchComments","code","message","captureElementContext","clientX","clientY","el","docEl","pinX","pinY","rect","buildSelector","getDirectText","classStr","getSiblingsSummary","getNearbyText","parts","current","part","classes","text","node","cls","siblings","child","tag","depth","REVIEW_STYLES","PIN_DOCUMENT_STYLES","STYLE_ID","PinManager","__publicField","PIN_DOCUMENT_STYLES","pinNumber","x","y","isMine","pin","num","el","CommentCard","shadowRoot","__publicField","options","textarea","text","submitBtn","errorEl","err","ReviewMode","token","shadowRoot","initData","__publicField","PinManager","CommentCard","pinCount","e","comments","fetchComments","c","isMine","el","pinData","captureElementContext","pinNumber","text","submitComment","message","toast","TriageMode","token","shadowRoot","__publicField","PinManager","fetchComments","c","target","num","count","pinNumber","e","comment","time","dismiss","ev","Review","_options","__publicField","styleEl","REVIEW_STYLES","params","reviewToken","triageToken","data","validateToken","ReviewMode","err","ReviewError","TriageMode","message","overlay"]}
1
+ {"version":3,"sources":["../src/review/index.ts","../src/review/api.ts","../src/review/ElementCapture.ts","../src/review/styles.ts","../src/review/PinManager.ts","../src/review/CommentCard.ts","../src/review/ReviewMode.ts","../src/review/TriageMode.ts","../src/review/Review.ts"],"sourcesContent":["export { Review } from './Review'\nexport type { PinData, ReviewOptions } from './types'\n","import type { PinData, ReviewComment, ReviewInitData } from './types'\n\n// Default to production, override with env var for local dev\nconst API_URL = (() => {\n // Vite dev mode\n if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_URL) {\n return import.meta.env.VITE_API_URL\n }\n return 'https://api.ourroadmaps.com'\n})()\n\nexport async function validateToken(token: string): Promise<ReviewInitData> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}`)\n if (res.status === 410) throw new ReviewError('expired', 'This feedback session has expired')\n if (res.status === 404) throw new ReviewError('invalid', 'This review link is not valid')\n if (!res.ok) throw new ReviewError('error', 'Something went wrong')\n const body = await res.json()\n return body.data\n}\n\n// TODO: Wire into SDK — show name prompt on first visit, call identify() to transition invite status from 'pending' to 'opened'\nexport async function identify(token: string, name: string): Promise<void> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/identify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to identify')\n}\n\nexport async function submitComment(\n token: string,\n comment: { commentText: string; pinNumber: number | null; pinData: PinData | null; pageUrl: string | null },\n): Promise<{ id: string; pinNumber: number | null; createdAt: string }> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(comment),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to submit comment')\n const body = await res.json()\n return body.data\n}\n\nexport async function fetchComments(token: string): Promise<ReviewComment[]> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`)\n if (!res.ok) throw new ReviewError('error', 'Failed to load comments')\n const body = await res.json()\n return body.data\n}\n\nexport class ReviewError extends Error {\n constructor(\n public code: 'expired' | 'invalid' | 'error',\n message: string,\n ) {\n super(message)\n this.name = 'ReviewError'\n }\n}\n","import type { PinData } from './types'\n\nexport function captureElementContext(clientX: number, clientY: number): PinData {\n const el = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n const docEl = document.documentElement\n\n const pinX = ((clientX + window.scrollX) / docEl.scrollWidth) * 100\n const pinY = ((clientY + window.scrollY) / docEl.scrollHeight) * 100\n\n if (!el || el === document.body || el === docEl) {\n return {\n pinX,\n pinY,\n element: {\n selector: 'body',\n tag: 'body',\n text: '',\n ariaLabel: null,\n className: '',\n boundingBox: { x: 0, y: 0, w: docEl.scrollWidth, h: docEl.scrollHeight },\n },\n context: {\n parentTag: '',\n parentText: '',\n grandparentTag: '',\n siblings: [],\n nearbyText: '',\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n const rect = el.getBoundingClientRect()\n\n return {\n pinX,\n pinY,\n element: {\n selector: buildSelector(el),\n tag: el.tagName.toLowerCase(),\n text: getDirectText(el).slice(0, 200),\n ariaLabel: el.getAttribute('aria-label'),\n className: el.className && typeof el.className === 'string' ? el.className : '',\n boundingBox: {\n x: Math.round(rect.x),\n y: Math.round(rect.y),\n w: Math.round(rect.width),\n h: Math.round(rect.height),\n },\n },\n context: {\n parentTag: el.parentElement ? `${el.parentElement.tagName.toLowerCase()}${classStr(el.parentElement)}` : '',\n parentText: el.parentElement ? getDirectText(el.parentElement).slice(0, 100) : '',\n grandparentTag: el.parentElement?.parentElement\n ? `${el.parentElement.parentElement.tagName.toLowerCase()}${classStr(el.parentElement.parentElement)}`\n : '',\n siblings: getSiblingsSummary(el),\n nearbyText: getNearbyText(el).slice(0, 200),\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\nfunction buildSelector(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(`#${current.id}`)\n break\n }\n\n let part = current.tagName.toLowerCase()\n if (current.className && typeof current.className === 'string') {\n const classes = current.className.trim().split(/\\s+/).slice(0, 2).join('.')\n if (classes) part += `.${classes}`\n }\n\n parts.unshift(part)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getDirectText(el: HTMLElement): string {\n let text = ''\n for (const node of el.childNodes) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent?.trim() || ''\n }\n }\n return text.trim() || el.textContent?.trim().slice(0, 200) || ''\n}\n\nfunction classStr(el: HTMLElement): string {\n if (!el.className || typeof el.className !== 'string') return ''\n const cls = el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n return cls ? `.${cls}` : ''\n}\n\nfunction getSiblingsSummary(el: HTMLElement): string[] {\n if (!el.parentElement) return []\n const siblings: string[] = []\n for (const child of el.parentElement.children) {\n if (child === el) continue\n const tag = child.tagName.toLowerCase()\n const text = (child.textContent?.trim() || '').slice(0, 50)\n siblings.push(text ? `${tag}:${text}` : tag)\n if (siblings.length >= 4) break\n }\n return siblings\n}\n\nfunction getNearbyText(el: HTMLElement): string {\n // Walk up to find the nearest container with meaningful text\n let current: HTMLElement | null = el.parentElement\n let depth = 0\n while (current && depth < 3) {\n const text = getDirectText(current)\n if (text && text !== getDirectText(el)) return text\n current = current.parentElement\n depth++\n }\n return ''\n}\n","export const REVIEW_STYLES = `\n:host {\n all: initial;\n}\n\n/* ─── Pin Container (rendered outside shadow DOM) ─── */\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n/* ─── Pin Marker ─── */\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n/* ─── Toolbar ─── */\n.review-toolbar {\n position: fixed;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n border-radius: 12px 12px 0 0;\n padding: 10px 20px;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n z-index: 10001;\n backdrop-filter: blur(8px);\n box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);\n}\n\n/* ─── Comment Card ─── */\n.review-comment-card {\n position: fixed;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);\n width: 300px;\n padding: 16px;\n z-index: 10002;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Comment Textarea ─── */\n.review-comment-input {\n width: 100%;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n padding: 10px 12px;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n color: #1f2937;\n background: #fafafa;\n box-sizing: border-box;\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.review-comment-input:focus {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);\n background: #fff;\n}\n\n.review-comment-input::placeholder {\n color: #9ca3af;\n}\n\n.review-comment-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ─── Comment Actions ─── */\n.review-comment-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n}\n\n/* ─── Buttons ─── */\n.review-btn {\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n border: none;\n transition: background 0.15s ease, opacity 0.15s ease;\n}\n\n.review-btn--cancel {\n color: #6b7280;\n background: transparent;\n}\n\n.review-btn--cancel:hover {\n background: #f3f4f6;\n}\n\n.review-btn--submit {\n background: #7c3aed;\n color: #fff;\n}\n\n.review-btn--submit:hover {\n background: #6d28d9;\n}\n\n.review-btn--submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ─── Comment Error ─── */\n.review-comment-error {\n color: #dc2626;\n font-size: 12px;\n margin-top: 8px;\n font-family: system-ui, -apple-system, sans-serif;\n}\n\n/* ─── Name Prompt ─── */\n.review-prompt {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: #fff;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.08);\n border-radius: 12px;\n padding: 28px 32px;\n z-index: 10002;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.06);\n max-width: 360px;\n width: 90%;\n}\n\n/* ─── Expired Overlay ─── */\n.review-expired-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10003;\n backdrop-filter: blur(2px);\n}\n\n.review-expired-card {\n background: #fff;\n border-radius: 12px;\n padding: 28px 32px;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(0, 0, 0, 0.06);\n}\n\n/* ─── Toast ─── */\n.review-toast {\n position: fixed;\n bottom: 80px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n padding: 10px 20px;\n border-radius: 8px;\n z-index: 10003;\n pointer-events: none;\n animation: review-toast-fade 2.5s ease forwards;\n backdrop-filter: blur(8px);\n}\n\n@keyframes review-toast-fade {\n 0% { opacity: 0; transform: translateX(-50%) translateY(8px); }\n 10% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-4px); }\n}\n\n/* ─── Tooltip ─── */\n.review-tooltip {\n position: absolute;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n border-radius: 6px;\n padding: 8px 12px;\n max-width: 280px;\n z-index: 10002;\n pointer-events: none;\n backdrop-filter: blur(8px);\n}\n\n/* ─── Reduced Motion ─── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n`\n\n/**\n * Subset of styles for pins that render outside the shadow DOM.\n * Injected into document.head by PinManager.\n */\nexport const PIN_DOCUMENT_STYLES = `\nbody {\n position: relative !important;\n}\n\n.pin-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n min-height: 100%;\n pointer-events: none;\n z-index: 10000;\n}\n\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .review-pin, .review-pin:hover, .review-pin--highlighted {\n transition-duration: 0.01ms !important;\n }\n}\n`\n","import { PIN_DOCUMENT_STYLES } from './styles'\n\nconst STYLE_ID = 'roadmaps-review-pin-styles'\n\nexport class PinManager {\n private container: HTMLDivElement\n private pins: Map<number, HTMLElement> = new Map()\n private styleEl: HTMLStyleElement | null = null\n\n constructor() {\n this.container = document.createElement('div')\n this.container.className = 'pin-container'\n }\n\n mount(): void {\n // Inject pin styles into document.head (pins live outside shadow DOM)\n if (!document.getElementById(STYLE_ID)) {\n this.styleEl = document.createElement('style')\n this.styleEl.id = STYLE_ID\n this.styleEl.textContent = PIN_DOCUMENT_STYLES\n document.head.appendChild(this.styleEl)\n }\n\n document.body.appendChild(this.container)\n }\n\n addPin(pinNumber: number, x: number, y: number, isMine: boolean): void {\n const pin = document.createElement('div')\n pin.className = isMine ? 'review-pin' : 'review-pin review-pin--other'\n pin.textContent = String(pinNumber)\n pin.style.left = `${x}%`\n pin.style.top = `${y}%`\n pin.dataset.pinNumber = String(pinNumber)\n this.container.appendChild(pin)\n this.pins.set(pinNumber, pin)\n }\n\n removePin(pinNumber: number): void {\n const pin = this.pins.get(pinNumber)\n if (pin) {\n pin.remove()\n this.pins.delete(pinNumber)\n }\n }\n\n highlightPin(pinNumber: number): void {\n for (const [num, el] of this.pins) {\n el.classList.toggle('review-pin--highlighted', num === pinNumber)\n }\n }\n\n clearHighlight(): void {\n for (const el of this.pins.values()) {\n el.classList.remove('review-pin--highlighted')\n }\n }\n\n destroy(): void {\n this.container.remove()\n this.pins.clear()\n\n // Clean up injected style element\n if (this.styleEl) {\n this.styleEl.remove()\n this.styleEl = null\n } else {\n document.getElementById(STYLE_ID)?.remove()\n }\n }\n}\n","export class CommentCard {\n private card: HTMLElement | null = null\n private onSubmit: ((text: string) => Promise<void>) | null = null\n private onCancel: (() => void) | null = null\n\n constructor(private shadowRoot: ShadowRoot) {}\n\n show(options: {\n x: number\n y: number\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n this.hide()\n this.onSubmit = options.onSubmit\n this.onCancel = options.onCancel\n\n this.card = document.createElement('div')\n this.card.className = 'review-comment-card'\n // Position near click point, adjust to stay in viewport\n this.card.style.left = `${Math.min(options.x, window.innerWidth - 320)}px`\n this.card.style.top = `${Math.min(options.y + 20, window.innerHeight - 200)}px`\n\n this.card.innerHTML = `\n <textarea class=\"review-comment-input\" placeholder=\"Leave your feedback...\" rows=\"3\"></textarea>\n <div class=\"review-comment-actions\">\n <button class=\"review-btn review-btn--cancel\">Cancel</button>\n <button class=\"review-btn review-btn--submit\">Submit</button>\n </div>\n <div class=\"review-comment-error\" style=\"display:none\"></div>\n `\n\n this.attachListeners()\n this.shadowRoot.appendChild(this.card)\n\n const textarea = this.card.querySelector('textarea')\n textarea?.focus()\n }\n\n hide(): void {\n this.card?.remove()\n this.card = null\n }\n\n showForGeneral(options: {\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n // Show centered for general (non-pinned) comments\n this.show({\n x: window.innerWidth / 2 - 150,\n y: window.innerHeight / 2 - 100,\n onSubmit: options.onSubmit,\n onCancel: options.onCancel,\n })\n }\n\n private attachListeners(): void {\n if (!this.card) return\n\n this.card.querySelector('.review-btn--cancel')?.addEventListener('click', () => {\n this.onCancel?.()\n this.hide()\n })\n\n this.card.querySelector('.review-btn--submit')?.addEventListener('click', () => this.handleSubmit())\n\n this.card.querySelector('textarea')?.addEventListener('keydown', (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n private async handleSubmit(): Promise<void> {\n if (!this.card) return\n const textarea = this.card.querySelector('textarea') as HTMLTextAreaElement\n const text = textarea.value.trim()\n if (!text) return\n\n const submitBtn = this.card.querySelector('.review-btn--submit') as HTMLButtonElement\n const errorEl = this.card.querySelector('.review-comment-error') as HTMLElement\n submitBtn.disabled = true\n submitBtn.textContent = 'Submitting...'\n textarea.disabled = true\n errorEl.style.display = 'none'\n\n try {\n await this.onSubmit?.(text)\n this.hide()\n } catch (err) {\n submitBtn.disabled = false\n submitBtn.textContent = 'Submit'\n textarea.disabled = false\n errorEl.textContent = err instanceof Error ? err.message : 'Failed to submit'\n errorEl.style.display = 'block'\n }\n }\n}\n","import { submitComment, fetchComments } from './api'\nimport { captureElementContext } from './ElementCapture'\nimport { PinManager } from './PinManager'\nimport { CommentCard } from './CommentCard'\nimport type { ReviewInitData } from './types'\n\nexport class ReviewMode {\n private pinManager: PinManager\n private commentCard: CommentCard\n private nextPinNumber: number\n private pendingPinNumber: number | null = null\n private promptEl: HTMLElement | null = null\n private toolbarEl: HTMLElement | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n private initData: ReviewInitData,\n ) {\n this.pinManager = new PinManager()\n this.commentCard = new CommentCard(shadowRoot)\n this.nextPinNumber = initData.nextPinNumber\n }\n\n async init(): Promise<void> {\n // Set crosshair cursor on body\n document.body.style.cursor = 'crosshair'\n\n // Mount pin manager\n this.pinManager.mount()\n\n // Render first-visit prompt\n this.showPrompt()\n\n // Render bottom toolbar\n this.renderToolbar()\n\n // Fetch and render existing pins\n await this.loadExistingPins()\n\n // Listen for clicks to drop pins\n this.clickHandler = (e: MouseEvent) => this.handleClick(e)\n document.addEventListener('click', this.clickHandler, true)\n }\n\n private showPrompt(): void {\n this.promptEl = document.createElement('div')\n this.promptEl.className = 'review-prompt'\n this.promptEl.innerHTML = `\n <h3 style=\"margin:0 0 8px;font-size:16px;\">Click anywhere to leave feedback</h3>\n <p style=\"margin:0;color:#666;font-size:14px;\">Drop numbered pins on elements you want to comment on</p>\n `\n this.shadowRoot.appendChild(this.promptEl)\n\n // Auto-dismiss after 5 seconds\n setTimeout(() => this.dismissPrompt(), 5000)\n }\n\n private dismissPrompt(): void {\n if (this.promptEl) {\n this.promptEl.remove()\n this.promptEl = null\n }\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n this.updateToolbar()\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private updateToolbar(): void {\n if (!this.toolbarEl) return\n const pinCount = this.nextPinNumber - 1\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">${this.initData.session.name}</span>\n <span style=\"opacity:0.7;\">${pinCount} pin${pinCount !== 1 ? 's' : ''}</span>\n <button class=\"review-btn review-btn--submit\" style=\"margin-left:auto;padding:6px 12px;font-size:13px;\">General Comment</button>\n `\n this.toolbarEl.querySelector('button')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleGeneralComment()\n })\n }\n\n private async loadExistingPins(): Promise<void> {\n try {\n const comments = await fetchComments(this.token)\n for (const c of comments) {\n if (c.pinNumber != null && c.pinData) {\n // commentText being non-null means it's the current reviewer's pin\n const isMine = c.commentText != null\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine)\n }\n }\n } catch {\n // Silently fail - pins just won't show up\n }\n }\n\n private handleClick(e: MouseEvent): void {\n // Don't intercept clicks on our own shadow DOM elements\n const path = e.composedPath()\n if (path.some((el) => el instanceof HTMLElement && el.closest?.('#ourroadmaps-review'))) return\n\n // Don't intercept if comment card is open\n if (this.pendingPinNumber != null) return\n\n // Dismiss first-visit prompt on first click\n this.dismissPrompt()\n\n const pinData = captureElementContext(e.clientX, e.clientY)\n const pinNumber = this.nextPinNumber\n\n // Add pin at click position\n this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true)\n this.pendingPinNumber = pinNumber\n\n // Show comment card\n this.commentCard.show({\n x: e.clientX,\n y: e.clientY,\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber,\n pinData,\n pageUrl: window.location.pathname,\n })\n this.pendingPinNumber = null\n this.nextPinNumber++\n this.updateToolbar()\n this.showToast('Comment saved')\n },\n onCancel: () => {\n // Remove the pending pin\n this.pinManager.removePin(pinNumber)\n this.pendingPinNumber = null\n },\n })\n\n e.preventDefault()\n e.stopPropagation()\n }\n\n private handleGeneralComment(): void {\n if (this.pendingPinNumber != null) return\n\n this.commentCard.showForGeneral({\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber: null,\n pinData: null,\n pageUrl: window.location.pathname,\n })\n this.showToast('Comment saved')\n },\n onCancel: () => {},\n })\n }\n\n private showToast(message: string): void {\n const toast = document.createElement('div')\n toast.className = 'review-toast'\n toast.textContent = message\n this.shadowRoot.appendChild(toast)\n setTimeout(() => toast.remove(), 2500)\n }\n\n destroy(): void {\n document.body.style.cursor = ''\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n }\n this.dismissPrompt()\n this.toolbarEl?.remove()\n this.commentCard.hide()\n this.pinManager.destroy()\n }\n}\n","import { fetchComments } from './api'\nimport { PinManager } from './PinManager'\nimport type { ReviewComment } from './types'\n\nexport class TriageMode {\n private pinManager: PinManager\n private toolbarEl: HTMLElement | null = null\n private tooltipEl: HTMLElement | null = null\n private comments: ReviewComment[] = []\n private pinClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n ) {\n this.pinManager = new PinManager()\n }\n\n async init(): Promise<void> {\n this.pinManager.mount()\n\n // Fetch all comments\n try {\n this.comments = await fetchComments(this.token)\n for (const c of this.comments) {\n if (c.pinNumber != null && c.pinData) {\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true)\n }\n }\n } catch {\n // Show error state\n }\n\n this.renderToolbar()\n\n // Listen for clicks on pins\n this.pinClickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.classList?.contains('review-pin')) {\n const num = Number(target.dataset.pinNumber)\n if (num) this.handlePinClick(num, e)\n }\n }\n document.addEventListener('click', this.pinClickHandler, true)\n\n // Auto-focus pin from URL param\n this.autoFocusPin()\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n const count = this.comments.filter((c) => c.pinNumber != null).length\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">Triage Mode</span>\n <span style=\"opacity:0.7;\">${count} pin${count !== 1 ? 's' : ''}</span>\n `\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private handlePinClick(pinNumber: number, e: MouseEvent): void {\n this.hideTooltip()\n this.pinManager.highlightPin(pinNumber)\n this.showTooltipForPin(pinNumber, e.clientX + 16, e.clientY - 10)\n }\n\n private showTooltipForPin(pinNumber: number, x?: number, y?: number): void {\n this.hideTooltip()\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment) return\n\n this.tooltipEl = document.createElement('div')\n this.tooltipEl.className = 'review-tooltip'\n\n const time = new Date(comment.createdAt).toLocaleString()\n\n // Build tooltip with textContent to prevent XSS\n const header = document.createElement('div')\n header.style.cssText = 'font-weight:500;margin-bottom:4px;'\n header.textContent = `Pin #${pinNumber}`\n this.tooltipEl.appendChild(header)\n\n if (comment.reviewerName) {\n const name = document.createElement('div')\n name.style.cssText = 'font-size:11px;opacity:0.7;margin-bottom:2px;'\n name.textContent = comment.reviewerName\n this.tooltipEl.appendChild(name)\n }\n\n const text = document.createElement('div')\n text.style.cssText = 'margin-bottom:4px;'\n text.textContent = comment.commentText || '(no text)'\n this.tooltipEl.appendChild(text)\n\n const timeEl = document.createElement('div')\n timeEl.style.cssText = 'font-size:11px;opacity:0.7;'\n timeEl.textContent = time\n this.tooltipEl.appendChild(timeEl)\n\n // Position: use provided coords or center of viewport\n const posX = x ?? window.innerWidth / 2\n const posY = y ?? window.innerHeight / 3\n this.tooltipEl.style.position = 'fixed'\n this.tooltipEl.style.left = `${Math.min(posX, window.innerWidth - 300)}px`\n this.tooltipEl.style.top = `${Math.min(posY, window.innerHeight - 150)}px`\n this.shadowRoot.appendChild(this.tooltipEl)\n\n // Click elsewhere to dismiss\n const dismiss = (ev: MouseEvent) => {\n if (ev.target !== this.tooltipEl && !this.tooltipEl?.contains(ev.target as Node)) {\n this.hideTooltip()\n this.pinManager.clearHighlight()\n document.removeEventListener('click', dismiss, true)\n }\n }\n setTimeout(() => document.addEventListener('click', dismiss, true), 0)\n }\n\n private autoFocusPin(): void {\n const params = new URLSearchParams(window.location.search)\n const pinParam = params.get('pin')\n if (!pinParam) return\n\n const pinNumber = Number(pinParam)\n if (!pinNumber) return\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment?.pinData) return\n\n // Scroll to pin position (pinX/pinY are document-relative percentages)\n const docEl = document.documentElement\n const scrollX = (comment.pinData.pinX / 100) * docEl.scrollWidth\n const scrollY = (comment.pinData.pinY / 100) * docEl.scrollHeight\n\n window.scrollTo({\n left: scrollX - window.innerWidth / 2,\n top: scrollY - window.innerHeight / 2,\n behavior: 'smooth',\n })\n\n // Open tooltip after scroll completes\n setTimeout(() => {\n this.pinManager.highlightPin(pinNumber)\n this.showTooltipForPin(pinNumber)\n }, 500)\n }\n\n private hideTooltip(): void {\n this.tooltipEl?.remove()\n this.tooltipEl = null\n }\n\n destroy(): void {\n if (this.pinClickHandler) {\n document.removeEventListener('click', this.pinClickHandler, true)\n }\n this.hideTooltip()\n this.toolbarEl?.remove()\n this.pinManager.destroy()\n }\n}\n","import { validateToken, ReviewError } from './api'\nimport { ReviewMode } from './ReviewMode'\nimport { TriageMode } from './TriageMode'\nimport { REVIEW_STYLES } from './styles'\nimport type { ReviewOptions } from './types'\n\nexport class Review {\n private root: HTMLElement\n private shadow: ShadowRoot\n private mode: ReviewMode | TriageMode | null = null\n private _isDestroyed = false\n\n constructor(_options: ReviewOptions = {}) {\n this.root = document.createElement('div')\n this.root.id = 'ourroadmaps-review'\n this.shadow = this.root.attachShadow({ mode: 'open' })\n\n const styleEl = document.createElement('style')\n styleEl.textContent = REVIEW_STYLES\n this.shadow.appendChild(styleEl)\n\n document.body.appendChild(this.root)\n }\n\n async init(): Promise<void> {\n const params = new URLSearchParams(window.location.search)\n const reviewToken = params.get('review')\n const triageToken = params.get('triage')\n\n if (reviewToken) {\n try {\n const data = await validateToken(reviewToken)\n this.mode = new ReviewMode(reviewToken, this.shadow, data)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n } else if (triageToken) {\n try {\n await validateToken(triageToken)\n this.mode = new TriageMode(triageToken, this.shadow)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n }\n }\n\n private showErrorOverlay(message: string): void {\n const overlay = document.createElement('div')\n overlay.className = 'review-expired-overlay'\n overlay.innerHTML = `\n <div class=\"review-expired-card\">\n <h2 style=\"margin:0 0 8px;font-size:18px;\">Session Unavailable</h2>\n <p style=\"margin:0;color:#666;font-size:14px;\">${message}</p>\n <p style=\"margin:12px 0 0;color:#999;font-size:13px;\">Contact the prototype owner for a new link.</p>\n </div>\n `\n this.shadow.appendChild(overlay)\n\n // Apply grayscale to body\n document.body.style.filter = 'grayscale(0.8)'\n }\n\n destroy(): void {\n if (this._isDestroyed) return\n this._isDestroyed = true\n this.mode?.destroy()\n this.root.remove()\n document.body.style.filter = ''\n }\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["review_exports","__export","Review","import_meta","API_URL","validateToken","token","res","ReviewError","submitComment","token","comment","res","API_URL","ReviewError","fetchComments","code","message","captureElementContext","clientX","clientY","el","docEl","pinX","pinY","rect","buildSelector","getDirectText","classStr","getSiblingsSummary","getNearbyText","parts","current","part","classes","text","node","cls","siblings","child","tag","depth","REVIEW_STYLES","PIN_DOCUMENT_STYLES","STYLE_ID","PinManager","__publicField","PIN_DOCUMENT_STYLES","pinNumber","x","y","isMine","pin","num","el","CommentCard","shadowRoot","__publicField","options","textarea","text","submitBtn","errorEl","err","ReviewMode","token","shadowRoot","initData","__publicField","PinManager","CommentCard","pinCount","e","comments","fetchComments","c","isMine","el","pinData","captureElementContext","pinNumber","text","submitComment","message","toast","TriageMode","token","shadowRoot","__publicField","PinManager","fetchComments","c","target","num","count","pinNumber","e","x","y","comment","time","header","name","text","timeEl","posX","posY","dismiss","ev","pinParam","docEl","scrollX","scrollY","Review","_options","__publicField","styleEl","REVIEW_STYLES","params","reviewToken","triageToken","data","validateToken","ReviewMode","err","ReviewError","TriageMode","message","overlay"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ourroadmaps/web-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Feedback widget and overlay system for OurRoadmaps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/review/api.ts","../src/review/ElementCapture.ts","../src/review/styles.ts","../src/review/PinManager.ts","../src/review/CommentCard.ts","../src/review/ReviewMode.ts","../src/review/TriageMode.ts","../src/review/Review.ts"],"names":["__publicField"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAyQtB,IAAM,mBAAA,GAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;;;ACvQnC,IAAM,QAAA,GAAW,4BAAA;AAEV,IAAM,aAAN,MAAiB;AAAA,EAKtB,WAAA,GAAc;AAJd,IAAAA,+BAAA,CAAA,IAAA,EAAQ,WAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,MAAA,sBAAqC,GAAA,EAAI,CAAA;AACjD,IAAAA,+BAAA,CAAA,IAAA,EAAQ,SAAA,EAAmC,IAAA,CAAA;AAGzC,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,eAAA;AAAA,EAC7B;AAAA,EAEA,KAAA,GAAc;AAEZ,IAAA,IAAI,CAAC,QAAA,CAAS,cAAA,CAAe,QAAQ,CAAA,EAAG;AACtC,MAAA,IAAA,CAAK,OAAA,GAAU,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC7C,MAAA,IAAA,CAAK,QAAQ,EAAA,GAAK,QAAA;AAClB,MAAA,IAAA,CAAK,QAAQ,WAAA,GAAc,mBAAA;AAC3B,MAAA,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AAAA,IACxC;AAEA,IAAA,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA;AAAA,EAC1C;AAAA,EAEA,MAAA,CAAO,SAAA,EAAmB,CAAA,EAAW,CAAA,EAAW,MAAA,EAAuB;AACrE,IAAA,MAAM,GAAA,GAAM,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACxC,IAAA,GAAA,CAAI,SAAA,GAAY,SAAS,YAAA,GAAe,8BAAA;AACxC,IAAA,GAAA,CAAI,WAAA,GAAc,OAAO,SAAS,CAAA;AAClC,IAAA,GAAA,CAAI,KAAA,CAAM,IAAA,GAAO,CAAA,EAAG,CAAC,CAAA,CAAA,CAAA;AACrB,IAAA,GAAA,CAAI,KAAA,CAAM,GAAA,GAAM,CAAA,EAAG,CAAC,CAAA,CAAA,CAAA;AACpB,IAAA,GAAA,CAAI,OAAA,CAAQ,SAAA,GAAY,MAAA,CAAO,SAAS,CAAA;AACxC,IAAA,IAAA,CAAK,SAAA,CAAU,YAAY,GAAG,CAAA;AAC9B,IAAA,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,GAAG,CAAA;AAAA,EAC9B;AAAA,EAEA,UAAU,SAAA,EAAyB;AACjC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA;AACnC,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,GAAA,CAAI,MAAA,EAAO;AACX,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,SAAS,CAAA;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,aAAa,SAAA,EAAyB;AACpC,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,EAAE,CAAA,IAAK,KAAK,IAAA,EAAM;AACjC,MAAA,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,yBAAA,EAA2B,GAAA,KAAQ,SAAS,CAAA;AAAA,IAClE;AAAA,EACF;AAAA,EAEA,cAAA,GAAuB;AACrB,IAAA,KAAA,MAAW,EAAA,IAAM,IAAA,CAAK,IAAA,CAAK,MAAA,EAAO,EAAG;AACnC,MAAA,EAAA,CAAG,SAAA,CAAU,OAAO,yBAAyB,CAAA;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,UAAU,MAAA,EAAO;AACtB,IAAA,IAAA,CAAK,KAAK,KAAA,EAAM;AAGhB,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,IAAA,CAAK,QAAQ,MAAA,EAAO;AACpB,MAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,IACjB,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,cAAA,CAAe,QAAQ,CAAA,EAAG,MAAA,EAAO;AAAA,IAC5C;AAAA,EACF;AACF,CAAA;;;ACrEO,IAAM,cAAN,MAAkB;AAAA,EAKvB,YAAoB,UAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAJpB,IAAAA,+BAAA,CAAA,IAAA,EAAQ,MAAA,EAA2B,IAAA,CAAA;AACnC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,UAAA,EAAqD,IAAA,CAAA;AAC7D,IAAAA,+BAAA,CAAA,IAAA,EAAQ,UAAA,EAAgC,IAAA,CAAA;AAAA,EAEK;AAAA,EAE7C,KAAK,OAAA,EAKI;AACP,IAAA,IAAA,CAAK,IAAA,EAAK;AACV,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AACxB,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AAExB,IAAA,IAAA,CAAK,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACxC,IAAA,IAAA,CAAK,KAAK,SAAA,GAAY,qBAAA;AAEtB,IAAA,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,CAAA,EAAG,MAAA,CAAO,UAAA,GAAa,GAAG,CAAC,CAAA,EAAA,CAAA;AACtE,IAAA,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,CAAA,GAAI,EAAA,EAAI,MAAA,CAAO,WAAA,GAAc,GAAG,CAAC,CAAA,EAAA,CAAA;AAE3E,IAAA,IAAA,CAAK,KAAK,SAAA,GAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAStB,IAAA,IAAA,CAAK,eAAA,EAAgB;AACrB,IAAA,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AAErC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,aAAA,CAAc,UAAU,CAAA;AACnD,IAAA,QAAA,EAAU,KAAA,EAAM;AAAA,EAClB;AAAA,EAEA,IAAA,GAAa;AACX,IAAA,IAAA,CAAK,MAAM,MAAA,EAAO;AAClB,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,eAAe,OAAA,EAGN;AAEP,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,CAAA,EAAG,MAAA,CAAO,UAAA,GAAa,CAAA,GAAI,GAAA;AAAA,MAC3B,CAAA,EAAG,MAAA,CAAO,WAAA,GAAc,CAAA,GAAI,GAAA;AAAA,MAC5B,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,UAAU,OAAA,CAAQ;AAAA,KACnB,CAAA;AAAA,EACH;AAAA,EAEQ,eAAA,GAAwB;AAC9B,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AAEhB,IAAA,IAAA,CAAK,KAAK,aAAA,CAAc,qBAAqB,CAAA,EAAG,gBAAA,CAAiB,SAAS,MAAM;AAC9E,MAAA,IAAA,CAAK,QAAA,IAAW;AAChB,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,CAAK,cAAc,qBAAqB,CAAA,EAAG,iBAAiB,OAAA,EAAS,MAAM,IAAA,CAAK,YAAA,EAAc,CAAA;AAEnG,IAAA,IAAA,CAAK,KAAK,aAAA,CAAc,UAAU,GAAG,gBAAA,CAAiB,SAAA,EAAW,CAAC,CAAA,KAAqB;AACrF,MAAA,IAAA,CAAK,EAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,CAAA,CAAE,QAAQ,OAAA,EAAS;AACjD,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,IAAA,CAAK,YAAA,EAAa;AAAA,MACpB;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAc,YAAA,GAA8B;AAC1C,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AAChB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,aAAA,CAAc,UAAU,CAAA;AACnD,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,KAAA,CAAM,IAAA,EAAK;AACjC,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,aAAA,CAAc,qBAAqB,CAAA;AAC/D,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,aAAA,CAAc,uBAAuB,CAAA;AAC/D,IAAA,SAAA,CAAU,QAAA,GAAW,IAAA;AACrB,IAAA,SAAA,CAAU,WAAA,GAAc,eAAA;AACxB,IAAA,QAAA,CAAS,QAAA,GAAW,IAAA;AACpB,IAAA,OAAA,CAAQ,MAAM,OAAA,GAAU,MAAA;AAExB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,WAAW,IAAI,CAAA;AAC1B,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,SAAS,GAAA,EAAK;AACZ,MAAA,SAAA,CAAU,QAAA,GAAW,KAAA;AACrB,MAAA,SAAA,CAAU,WAAA,GAAc,QAAA;AACxB,MAAA,QAAA,CAAS,QAAA,GAAW,KAAA;AACpB,MAAA,OAAA,CAAQ,WAAA,GAAc,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,kBAAA;AAC3D,MAAA,OAAA,CAAQ,MAAM,OAAA,GAAU,OAAA;AAAA,IAC1B;AAAA,EACF;AACF,CAAA;;;AC7FO,IAAM,aAAN,MAAiB;AAAA,EAStB,WAAA,CACU,KAAA,EACA,UAAA,EACA,QAAA,EACR;AAHQ,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAXV,IAAAA,+BAAA,CAAA,IAAA,EAAQ,YAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,aAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,eAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,kBAAA,EAAkC,IAAA,CAAA;AAC1C,IAAAA,+BAAA,CAAA,IAAA,EAAQ,UAAA,EAA+B,IAAA,CAAA;AACvC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,WAAA,EAAgC,IAAA,CAAA;AACxC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,cAAA,EAAiD,IAAA,CAAA;AAOvD,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,UAAA,EAAW;AACjC,IAAA,IAAA,CAAK,WAAA,GAAc,IAAI,WAAA,CAAY,UAAU,CAAA;AAC7C,IAAA,IAAA,CAAK,gBAAgB,QAAA,CAAS,aAAA;AAAA,EAChC;AAAA,EAEA,MAAM,IAAA,GAAsB;AAE1B,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,WAAA;AAG7B,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AAGtB,IAAA,IAAA,CAAK,UAAA,EAAW;AAGhB,IAAA,IAAA,CAAK,aAAA,EAAc;AAGnB,IAAA,MAAM,KAAK,gBAAA,EAAiB;AAG5B,IAAA,IAAA,CAAK,YAAA,GAAe,CAAC,CAAA,KAAkB,IAAA,CAAK,YAAY,CAAC,CAAA;AACzD,IAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,IAAI,CAAA;AAAA,EAC5D;AAAA,EAEQ,UAAA,GAAmB;AACzB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC5C,IAAA,IAAA,CAAK,SAAS,SAAA,GAAY,eAAA;AAC1B,IAAA,IAAA,CAAK,SAAS,SAAA,GAAY;AAAA;AAAA;AAAA,IAAA,CAAA;AAI1B,IAAA,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,IAAA,CAAK,QAAQ,CAAA;AAGzC,IAAA,UAAA,CAAW,MAAM,IAAA,CAAK,aAAA,EAAc,EAAG,GAAI,CAAA;AAAA,EAC7C;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,IAAA,CAAK,SAAS,MAAA,EAAO;AACrB,MAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,IAClB;AAAA,EACF;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,gBAAA;AAC3B,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA;AAAA,EAC5C;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACrB,IAAA,MAAM,QAAA,GAAW,KAAK,aAAA,GAAgB,CAAA;AACtC,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY;AAAA,qCAAA,EACQ,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,IAAI,CAAA;AAAA,iCAAA,EAC9B,QAAQ,CAAA,IAAA,EAAO,QAAA,KAAa,CAAA,GAAI,MAAM,EAAE,CAAA;AAAA;AAAA,IAAA,CAAA;AAGvE,IAAA,IAAA,CAAK,UAAU,aAAA,CAAc,QAAQ,GAAG,gBAAA,CAAiB,OAAA,EAAS,CAAC,CAAA,KAAM;AACvE,MAAA,CAAA,CAAE,eAAA,EAAgB;AAClB,MAAA,IAAA,CAAK,oBAAA,EAAqB;AAAA,IAC5B,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAc,gBAAA,GAAkC;AAC9C,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,IAAA,CAAK,KAAK,CAAA;AAC/C,MAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,QAAA,IAAI,CAAA,CAAE,SAAA,IAAa,IAAA,IAAQ,CAAA,CAAE,OAAA,EAAS;AAEpC,UAAA,MAAM,MAAA,GAAS,EAAE,WAAA,IAAe,IAAA;AAChC,UAAA,IAAA,CAAK,UAAA,CAAW,MAAA,CAAO,CAAA,CAAE,SAAA,EAAW,CAAA,CAAE,QAAQ,IAAA,EAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AAAA,QAC5E;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,YAAY,CAAA,EAAqB;AAEvC,IAAA,MAAM,IAAA,GAAO,EAAE,YAAA,EAAa;AAC5B,IAAA,IAAI,IAAA,CAAK,IAAA,CAAK,CAAC,EAAA,KAAO,EAAA,YAAc,eAAe,EAAA,CAAG,OAAA,GAAU,qBAAqB,CAAC,CAAA,EAAG;AAGzF,IAAA,IAAI,IAAA,CAAK,oBAAoB,IAAA,EAAM;AAGnC,IAAA,IAAA,CAAK,aAAA,EAAc;AAEnB,IAAA,MAAM,OAAA,GAAU,qBAAA,CAAsB,CAAA,CAAE,OAAA,EAAS,EAAE,OAAO,CAAA;AAC1D,IAAA,MAAM,YAAY,IAAA,CAAK,aAAA;AAGvB,IAAA,IAAA,CAAK,WAAW,MAAA,CAAO,SAAA,EAAW,QAAQ,IAAA,EAAM,OAAA,CAAQ,MAAM,IAAI,CAAA;AAClE,IAAA,IAAA,CAAK,gBAAA,GAAmB,SAAA;AAGxB,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK;AAAA,MACpB,GAAG,CAAA,CAAE,OAAA;AAAA,MACL,GAAG,CAAA,CAAE,OAAA;AAAA,MACL,QAAA,EAAU,OAAO,IAAA,KAAiB;AAChC,QAAA,MAAM,aAAA,CAAc,KAAK,KAAA,EAAO;AAAA,UAC9B,WAAA,EAAa,IAAA;AAAA,UACb,SAAA;AAAA,UACA,OAAA;AAAA,UACA,OAAA,EAAS,OAAO,QAAA,CAAS;AAAA,SAC1B,CAAA;AACD,QAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AACxB,QAAA,IAAA,CAAK,aAAA,EAAA;AACL,QAAA,IAAA,CAAK,aAAA,EAAc;AACnB,QAAA,IAAA,CAAK,UAAU,eAAe,CAAA;AAAA,MAChC,CAAA;AAAA,MACA,UAAU,MAAM;AAEd,QAAA,IAAA,CAAK,UAAA,CAAW,UAAU,SAAS,CAAA;AACnC,QAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AAAA,MAC1B;AAAA,KACD,CAAA;AAED,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,CAAA,CAAE,eAAA,EAAgB;AAAA,EACpB;AAAA,EAEQ,oBAAA,GAA6B;AACnC,IAAA,IAAI,IAAA,CAAK,oBAAoB,IAAA,EAAM;AAEnC,IAAA,IAAA,CAAK,YAAY,cAAA,CAAe;AAAA,MAC9B,QAAA,EAAU,OAAO,IAAA,KAAiB;AAChC,QAAA,MAAM,aAAA,CAAc,KAAK,KAAA,EAAO;AAAA,UAC9B,WAAA,EAAa,IAAA;AAAA,UACb,SAAA,EAAW,IAAA;AAAA,UACX,OAAA,EAAS,IAAA;AAAA,UACT,OAAA,EAAS,OAAO,QAAA,CAAS;AAAA,SAC1B,CAAA;AACD,QAAA,IAAA,CAAK,UAAU,eAAe,CAAA;AAAA,MAChC,CAAA;AAAA,MACA,UAAU,MAAM;AAAA,MAAC;AAAA,KAClB,CAAA;AAAA,EACH;AAAA,EAEQ,UAAU,OAAA,EAAuB;AACvC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC1C,IAAA,KAAA,CAAM,SAAA,GAAY,cAAA;AAClB,IAAA,KAAA,CAAM,WAAA,GAAc,OAAA;AACpB,IAAA,IAAA,CAAK,UAAA,CAAW,YAAY,KAAK,CAAA;AACjC,IAAA,UAAA,CAAW,MAAM,KAAA,CAAM,MAAA,EAAO,EAAG,IAAI,CAAA;AAAA,EACvC;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,EAAA;AAC7B,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,IAAI,CAAA;AAAA,IAC/D;AACA,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,IAAA,CAAK,WAAW,MAAA,EAAO;AACvB,IAAA,IAAA,CAAK,YAAY,IAAA,EAAK;AACtB,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AAAA,EAC1B;AACF,CAAA;;;AClLO,IAAM,aAAN,MAAiB;AAAA,EAOtB,WAAA,CACU,OACA,UAAA,EACR;AAFQ,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AARV,IAAAA,+BAAA,CAAA,IAAA,EAAQ,YAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,WAAA,EAAgC,IAAA,CAAA;AACxC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,WAAA,EAAgC,IAAA,CAAA;AACxC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,YAA4B,EAAC,CAAA;AACrC,IAAAA,+BAAA,CAAA,IAAA,EAAQ,iBAAA,EAAoD,IAAA,CAAA;AAM1D,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,UAAA,EAAW;AAAA,EACnC;AAAA,EAEA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AAGtB,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,QAAA,GAAW,MAAM,aAAA,CAAc,IAAA,CAAK,KAAK,CAAA;AAC9C,MAAA,KAAA,MAAW,CAAA,IAAK,KAAK,QAAA,EAAU;AAC7B,QAAA,IAAI,CAAA,CAAE,SAAA,IAAa,IAAA,IAAQ,CAAA,CAAE,OAAA,EAAS;AACpC,UAAA,IAAA,CAAK,UAAA,CAAW,MAAA,CAAO,CAAA,CAAE,SAAA,EAAW,CAAA,CAAE,QAAQ,IAAA,EAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,QAC1E;AAAA,MACF;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,IAAA,CAAK,aAAA,EAAc;AAGnB,IAAA,IAAA,CAAK,eAAA,GAAkB,CAAC,CAAA,KAAkB;AACxC,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,IAAI,MAAA,CAAO,SAAA,EAAW,QAAA,CAAS,YAAY,CAAA,EAAG;AAC5C,QAAA,MAAM,GAAA,GAAM,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA;AAC3C,QAAA,IAAI,GAAA,EAAK,IAAA,CAAK,cAAA,CAAe,GAAA,EAAK,CAAC,CAAA;AAAA,MACrC;AAAA,IACF,CAAA;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,eAAA,EAAiB,IAAI,CAAA;AAAA,EAC/D;AAAA,EAEQ,aAAA,GAAsB;AAC5B,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,gBAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,SAAA,IAAa,IAAI,CAAA,CAAE,MAAA;AAC/D,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY;AAAA;AAAA,iCAAA,EAEI,KAAK,CAAA,IAAA,EAAO,KAAA,KAAU,CAAA,GAAI,MAAM,EAAE,CAAA;AAAA,IAAA,CAAA;AAEjE,IAAA,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA;AAAA,EAC5C;AAAA,EAEQ,cAAA,CAAe,WAAmB,CAAA,EAAqB;AAC7D,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,UAAA,CAAW,aAAa,SAAS,CAAA;AAEtC,IAAA,MAAM,OAAA,GAAU,KAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,cAAc,SAAS,CAAA;AACnE,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY,gBAAA;AAE3B,IAAA,MAAM,OAAO,IAAI,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAE,cAAA,EAAe;AACxD,IAAA,IAAA,CAAK,UAAU,SAAA,GAAY;AAAA,2DAAA,EAC8B,SAAS,CAAA;AAAA,sCAAA,EAC9B,OAAA,CAAQ,eAAe,WAAW,CAAA;AAAA,+CAAA,EACzB,IAAI,CAAA;AAAA,IAAA,CAAA;AAIjD,IAAA,IAAA,CAAK,SAAA,CAAU,MAAM,QAAA,GAAW,OAAA;AAChC,IAAA,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,IAAA,GAAO,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,EAAA,EAAI,MAAA,CAAO,UAAA,GAAa,GAAG,CAAC,CAAA,EAAA,CAAA;AAChF,IAAA,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,EAAA,EAAI,MAAA,CAAO,WAAA,GAAc,GAAG,CAAC,CAAA,EAAA,CAAA;AAChF,IAAA,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,IAAA,CAAK,SAAS,CAAA;AAG1C,IAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAAmB;AAClC,MAAA,IAAI,EAAA,CAAG,MAAA,KAAW,IAAA,CAAK,SAAA,IAAa,CAAC,KAAK,SAAA,EAAW,QAAA,CAAS,EAAA,CAAG,MAAc,CAAA,EAAG;AAChF,QAAA,IAAA,CAAK,WAAA,EAAY;AACjB,QAAA,IAAA,CAAK,WAAW,cAAA,EAAe;AAC/B,QAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,OAAA,EAAS,IAAI,CAAA;AAAA,MACrD;AAAA,IACF,CAAA;AACA,IAAA,UAAA,CAAW,MAAM,QAAA,CAAS,gBAAA,CAAiB,SAAS,OAAA,EAAS,IAAI,GAAG,CAAC,CAAA;AAAA,EACvE;AAAA,EAEQ,WAAA,GAAoB;AAC1B,IAAA,IAAA,CAAK,WAAW,MAAA,EAAO;AACvB,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AAAA,EACnB;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,eAAA,EAAiB,IAAI,CAAA;AAAA,IAClE;AACA,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,WAAW,MAAA,EAAO;AACvB,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AAAA,EAC1B;AACF,CAAA;;;AClGO,IAAM,SAAN,MAAa;AAAA,EAMlB,WAAA,CAAY,QAAA,GAA0B,EAAC,EAAG;AAL1C,IAAAA,+BAAA,CAAA,IAAA,EAAQ,MAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,QAAA,CAAA;AACR,IAAAA,+BAAA,CAAA,IAAA,EAAQ,MAAA,EAAuC,IAAA,CAAA;AAC/C,IAAAA,+BAAA,CAAA,IAAA,EAAQ,cAAA,EAAe,KAAA,CAAA;AAGrB,IAAA,IAAA,CAAK,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACxC,IAAA,IAAA,CAAK,KAAK,EAAA,GAAK,oBAAA;AACf,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,IAAA,CAAK,aAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AAErD,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC9C,IAAA,OAAA,CAAQ,WAAA,GAAc,aAAA;AACtB,IAAA,IAAA,CAAK,MAAA,CAAO,YAAY,OAAO,CAAA;AAE/B,IAAA,QAAA,CAAS,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AAAA,EACrC;AAAA,EAEA,MAAM,IAAA,GAAsB;AAC1B,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAA,CAAO,SAAS,MAAM,CAAA;AACzD,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AACvC,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AAEvC,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,GAAO,MAAM,aAAA,CAAc,WAAW,CAAA;AAC5C,QAAA,IAAA,CAAK,OAAO,IAAI,UAAA,CAAW,WAAA,EAAa,IAAA,CAAK,QAAQ,IAAI,CAAA;AACzD,QAAA,MAAM,IAAA,CAAK,KAAK,IAAA,EAAK;AAAA,MACvB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,eAAe,WAAA,EAAa;AAC9B,UAAA,IAAA,CAAK,gBAAA;AAAA,YACH,GAAA,CAAI,IAAA,KAAS,SAAA,GACT,mCAAA,GACA;AAAA,WACN;AAAA,QACF;AAAA,MACF;AAAA,IACF,WAAW,WAAA,EAAa;AACtB,MAAA,IAAI;AACF,QAAA,MAAM,cAAc,WAAW,CAAA;AAC/B,QAAA,IAAA,CAAK,IAAA,GAAO,IAAI,UAAA,CAAW,WAAA,EAAa,KAAK,MAAM,CAAA;AACnD,QAAA,MAAM,IAAA,CAAK,KAAK,IAAA,EAAK;AAAA,MACvB,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,eAAe,WAAA,EAAa;AAC9B,UAAA,IAAA,CAAK,gBAAA;AAAA,YACH,GAAA,CAAI,IAAA,KAAS,SAAA,GACT,mCAAA,GACA;AAAA,WACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAA,EAAuB;AAC9C,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AAC5C,IAAA,OAAA,CAAQ,SAAA,GAAY,wBAAA;AACpB,IAAA,OAAA,CAAQ,SAAA,GAAY;AAAA;AAAA;AAAA,uDAAA,EAGiC,OAAO,CAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAI5D,IAAA,IAAA,CAAK,MAAA,CAAO,YAAY,OAAO,CAAA;AAG/B,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,gBAAA;AAAA,EAC/B;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAI,KAAK,YAAA,EAAc;AACvB,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,IAAA,IAAA,CAAK,MAAM,OAAA,EAAQ;AACnB,IAAA,IAAA,CAAK,KAAK,MAAA,EAAO;AACjB,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,EAAA;AAAA,EAC/B;AACF","file":"chunk-46WI5367.cjs","sourcesContent":["import type { PinData, ReviewComment, ReviewInitData } from './types'\n\n// Default to production, override with env var for local dev\nconst API_URL = (() => {\n // Vite dev mode\n if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_URL) {\n return import.meta.env.VITE_API_URL\n }\n return 'https://api.ourroadmaps.com'\n})()\n\nexport async function validateToken(token: string): Promise<ReviewInitData> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}`)\n if (res.status === 410) throw new ReviewError('expired', 'This feedback session has expired')\n if (res.status === 404) throw new ReviewError('invalid', 'This review link is not valid')\n if (!res.ok) throw new ReviewError('error', 'Something went wrong')\n const body = await res.json()\n return body.data\n}\n\n// TODO: Wire into SDK — show name prompt on first visit, call identify() to transition invite status from 'pending' to 'opened'\nexport async function identify(token: string, name: string): Promise<void> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/identify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to identify')\n}\n\nexport async function submitComment(\n token: string,\n comment: { commentText: string; pinNumber: number | null; pinData: PinData | null; pageUrl: string | null },\n): Promise<{ id: string; pinNumber: number | null; createdAt: string }> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(comment),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to submit comment')\n const body = await res.json()\n return body.data\n}\n\nexport async function fetchComments(token: string): Promise<ReviewComment[]> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`)\n if (!res.ok) throw new ReviewError('error', 'Failed to load comments')\n const body = await res.json()\n return body.data\n}\n\nexport class ReviewError extends Error {\n constructor(\n public code: 'expired' | 'invalid' | 'error',\n message: string,\n ) {\n super(message)\n this.name = 'ReviewError'\n }\n}\n","import type { PinData } from './types'\n\nexport function captureElementContext(clientX: number, clientY: number): PinData {\n const el = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n const docEl = document.documentElement\n\n const pinX = ((clientX + window.scrollX) / docEl.scrollWidth) * 100\n const pinY = ((clientY + window.scrollY) / docEl.scrollHeight) * 100\n\n if (!el || el === document.body || el === docEl) {\n return {\n pinX,\n pinY,\n element: {\n selector: 'body',\n tag: 'body',\n text: '',\n ariaLabel: null,\n className: '',\n boundingBox: { x: 0, y: 0, w: docEl.scrollWidth, h: docEl.scrollHeight },\n },\n context: {\n parentTag: '',\n parentText: '',\n grandparentTag: '',\n siblings: [],\n nearbyText: '',\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n const rect = el.getBoundingClientRect()\n\n return {\n pinX,\n pinY,\n element: {\n selector: buildSelector(el),\n tag: el.tagName.toLowerCase(),\n text: getDirectText(el).slice(0, 200),\n ariaLabel: el.getAttribute('aria-label'),\n className: el.className && typeof el.className === 'string' ? el.className : '',\n boundingBox: {\n x: Math.round(rect.x),\n y: Math.round(rect.y),\n w: Math.round(rect.width),\n h: Math.round(rect.height),\n },\n },\n context: {\n parentTag: el.parentElement ? `${el.parentElement.tagName.toLowerCase()}${classStr(el.parentElement)}` : '',\n parentText: el.parentElement ? getDirectText(el.parentElement).slice(0, 100) : '',\n grandparentTag: el.parentElement?.parentElement\n ? `${el.parentElement.parentElement.tagName.toLowerCase()}${classStr(el.parentElement.parentElement)}`\n : '',\n siblings: getSiblingsSummary(el),\n nearbyText: getNearbyText(el).slice(0, 200),\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\nfunction buildSelector(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(`#${current.id}`)\n break\n }\n\n let part = current.tagName.toLowerCase()\n if (current.className && typeof current.className === 'string') {\n const classes = current.className.trim().split(/\\s+/).slice(0, 2).join('.')\n if (classes) part += `.${classes}`\n }\n\n parts.unshift(part)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getDirectText(el: HTMLElement): string {\n let text = ''\n for (const node of el.childNodes) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent?.trim() || ''\n }\n }\n return text.trim() || el.textContent?.trim().slice(0, 200) || ''\n}\n\nfunction classStr(el: HTMLElement): string {\n if (!el.className || typeof el.className !== 'string') return ''\n const cls = el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n return cls ? `.${cls}` : ''\n}\n\nfunction getSiblingsSummary(el: HTMLElement): string[] {\n if (!el.parentElement) return []\n const siblings: string[] = []\n for (const child of el.parentElement.children) {\n if (child === el) continue\n const tag = child.tagName.toLowerCase()\n const text = (child.textContent?.trim() || '').slice(0, 50)\n siblings.push(text ? `${tag}:${text}` : tag)\n if (siblings.length >= 4) break\n }\n return siblings\n}\n\nfunction getNearbyText(el: HTMLElement): string {\n // Walk up to find the nearest container with meaningful text\n let current: HTMLElement | null = el.parentElement\n let depth = 0\n while (current && depth < 3) {\n const text = getDirectText(current)\n if (text && text !== getDirectText(el)) return text\n current = current.parentElement\n depth++\n }\n return ''\n}\n","export const REVIEW_STYLES = `\n:host {\n all: initial;\n}\n\n/* ─── Pin Container (rendered outside shadow DOM) ─── */\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n/* ─── Pin Marker ─── */\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n/* ─── Toolbar ─── */\n.review-toolbar {\n position: fixed;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n border-radius: 12px 12px 0 0;\n padding: 10px 20px;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n z-index: 10001;\n backdrop-filter: blur(8px);\n box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);\n}\n\n/* ─── Comment Card ─── */\n.review-comment-card {\n position: fixed;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);\n width: 300px;\n padding: 16px;\n z-index: 10002;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Comment Textarea ─── */\n.review-comment-input {\n width: 100%;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n padding: 10px 12px;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n color: #1f2937;\n background: #fafafa;\n box-sizing: border-box;\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.review-comment-input:focus {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);\n background: #fff;\n}\n\n.review-comment-input::placeholder {\n color: #9ca3af;\n}\n\n.review-comment-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ─── Comment Actions ─── */\n.review-comment-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n}\n\n/* ─── Buttons ─── */\n.review-btn {\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n border: none;\n transition: background 0.15s ease, opacity 0.15s ease;\n}\n\n.review-btn--cancel {\n color: #6b7280;\n background: transparent;\n}\n\n.review-btn--cancel:hover {\n background: #f3f4f6;\n}\n\n.review-btn--submit {\n background: #7c3aed;\n color: #fff;\n}\n\n.review-btn--submit:hover {\n background: #6d28d9;\n}\n\n.review-btn--submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ─── Comment Error ─── */\n.review-comment-error {\n color: #dc2626;\n font-size: 12px;\n margin-top: 8px;\n font-family: system-ui, -apple-system, sans-serif;\n}\n\n/* ─── Name Prompt ─── */\n.review-prompt {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: #fff;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.08);\n border-radius: 12px;\n padding: 28px 32px;\n z-index: 10002;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.06);\n max-width: 360px;\n width: 90%;\n}\n\n/* ─── Expired Overlay ─── */\n.review-expired-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10003;\n backdrop-filter: blur(2px);\n}\n\n.review-expired-card {\n background: #fff;\n border-radius: 12px;\n padding: 28px 32px;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(0, 0, 0, 0.06);\n}\n\n/* ─── Toast ─── */\n.review-toast {\n position: fixed;\n bottom: 80px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n padding: 10px 20px;\n border-radius: 8px;\n z-index: 10003;\n pointer-events: none;\n animation: review-toast-fade 2.5s ease forwards;\n backdrop-filter: blur(8px);\n}\n\n@keyframes review-toast-fade {\n 0% { opacity: 0; transform: translateX(-50%) translateY(8px); }\n 10% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-4px); }\n}\n\n/* ─── Tooltip ─── */\n.review-tooltip {\n position: absolute;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n border-radius: 6px;\n padding: 8px 12px;\n max-width: 280px;\n z-index: 10002;\n pointer-events: none;\n backdrop-filter: blur(8px);\n}\n\n/* ─── Reduced Motion ─── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n`\n\n/**\n * Subset of styles for pins that render outside the shadow DOM.\n * Injected into document.head by PinManager.\n */\nexport const PIN_DOCUMENT_STYLES = `\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .review-pin, .review-pin:hover, .review-pin--highlighted {\n transition-duration: 0.01ms !important;\n }\n}\n`\n","import { PIN_DOCUMENT_STYLES } from './styles'\n\nconst STYLE_ID = 'roadmaps-review-pin-styles'\n\nexport class PinManager {\n private container: HTMLDivElement\n private pins: Map<number, HTMLElement> = new Map()\n private styleEl: HTMLStyleElement | null = null\n\n constructor() {\n this.container = document.createElement('div')\n this.container.className = 'pin-container'\n }\n\n mount(): void {\n // Inject pin styles into document.head (pins live outside shadow DOM)\n if (!document.getElementById(STYLE_ID)) {\n this.styleEl = document.createElement('style')\n this.styleEl.id = STYLE_ID\n this.styleEl.textContent = PIN_DOCUMENT_STYLES\n document.head.appendChild(this.styleEl)\n }\n\n document.body.appendChild(this.container)\n }\n\n addPin(pinNumber: number, x: number, y: number, isMine: boolean): void {\n const pin = document.createElement('div')\n pin.className = isMine ? 'review-pin' : 'review-pin review-pin--other'\n pin.textContent = String(pinNumber)\n pin.style.left = `${x}%`\n pin.style.top = `${y}%`\n pin.dataset.pinNumber = String(pinNumber)\n this.container.appendChild(pin)\n this.pins.set(pinNumber, pin)\n }\n\n removePin(pinNumber: number): void {\n const pin = this.pins.get(pinNumber)\n if (pin) {\n pin.remove()\n this.pins.delete(pinNumber)\n }\n }\n\n highlightPin(pinNumber: number): void {\n for (const [num, el] of this.pins) {\n el.classList.toggle('review-pin--highlighted', num === pinNumber)\n }\n }\n\n clearHighlight(): void {\n for (const el of this.pins.values()) {\n el.classList.remove('review-pin--highlighted')\n }\n }\n\n destroy(): void {\n this.container.remove()\n this.pins.clear()\n\n // Clean up injected style element\n if (this.styleEl) {\n this.styleEl.remove()\n this.styleEl = null\n } else {\n document.getElementById(STYLE_ID)?.remove()\n }\n }\n}\n","export class CommentCard {\n private card: HTMLElement | null = null\n private onSubmit: ((text: string) => Promise<void>) | null = null\n private onCancel: (() => void) | null = null\n\n constructor(private shadowRoot: ShadowRoot) {}\n\n show(options: {\n x: number\n y: number\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n this.hide()\n this.onSubmit = options.onSubmit\n this.onCancel = options.onCancel\n\n this.card = document.createElement('div')\n this.card.className = 'review-comment-card'\n // Position near click point, adjust to stay in viewport\n this.card.style.left = `${Math.min(options.x, window.innerWidth - 320)}px`\n this.card.style.top = `${Math.min(options.y + 20, window.innerHeight - 200)}px`\n\n this.card.innerHTML = `\n <textarea class=\"review-comment-input\" placeholder=\"Leave your feedback...\" rows=\"3\"></textarea>\n <div class=\"review-comment-actions\">\n <button class=\"review-btn review-btn--cancel\">Cancel</button>\n <button class=\"review-btn review-btn--submit\">Submit</button>\n </div>\n <div class=\"review-comment-error\" style=\"display:none\"></div>\n `\n\n this.attachListeners()\n this.shadowRoot.appendChild(this.card)\n\n const textarea = this.card.querySelector('textarea')\n textarea?.focus()\n }\n\n hide(): void {\n this.card?.remove()\n this.card = null\n }\n\n showForGeneral(options: {\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n // Show centered for general (non-pinned) comments\n this.show({\n x: window.innerWidth / 2 - 150,\n y: window.innerHeight / 2 - 100,\n onSubmit: options.onSubmit,\n onCancel: options.onCancel,\n })\n }\n\n private attachListeners(): void {\n if (!this.card) return\n\n this.card.querySelector('.review-btn--cancel')?.addEventListener('click', () => {\n this.onCancel?.()\n this.hide()\n })\n\n this.card.querySelector('.review-btn--submit')?.addEventListener('click', () => this.handleSubmit())\n\n this.card.querySelector('textarea')?.addEventListener('keydown', (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n private async handleSubmit(): Promise<void> {\n if (!this.card) return\n const textarea = this.card.querySelector('textarea') as HTMLTextAreaElement\n const text = textarea.value.trim()\n if (!text) return\n\n const submitBtn = this.card.querySelector('.review-btn--submit') as HTMLButtonElement\n const errorEl = this.card.querySelector('.review-comment-error') as HTMLElement\n submitBtn.disabled = true\n submitBtn.textContent = 'Submitting...'\n textarea.disabled = true\n errorEl.style.display = 'none'\n\n try {\n await this.onSubmit?.(text)\n this.hide()\n } catch (err) {\n submitBtn.disabled = false\n submitBtn.textContent = 'Submit'\n textarea.disabled = false\n errorEl.textContent = err instanceof Error ? err.message : 'Failed to submit'\n errorEl.style.display = 'block'\n }\n }\n}\n","import { submitComment, fetchComments } from './api'\nimport { captureElementContext } from './ElementCapture'\nimport { PinManager } from './PinManager'\nimport { CommentCard } from './CommentCard'\nimport type { ReviewInitData } from './types'\n\nexport class ReviewMode {\n private pinManager: PinManager\n private commentCard: CommentCard\n private nextPinNumber: number\n private pendingPinNumber: number | null = null\n private promptEl: HTMLElement | null = null\n private toolbarEl: HTMLElement | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n private initData: ReviewInitData,\n ) {\n this.pinManager = new PinManager()\n this.commentCard = new CommentCard(shadowRoot)\n this.nextPinNumber = initData.nextPinNumber\n }\n\n async init(): Promise<void> {\n // Set crosshair cursor on body\n document.body.style.cursor = 'crosshair'\n\n // Mount pin manager\n this.pinManager.mount()\n\n // Render first-visit prompt\n this.showPrompt()\n\n // Render bottom toolbar\n this.renderToolbar()\n\n // Fetch and render existing pins\n await this.loadExistingPins()\n\n // Listen for clicks to drop pins\n this.clickHandler = (e: MouseEvent) => this.handleClick(e)\n document.addEventListener('click', this.clickHandler, true)\n }\n\n private showPrompt(): void {\n this.promptEl = document.createElement('div')\n this.promptEl.className = 'review-prompt'\n this.promptEl.innerHTML = `\n <h3 style=\"margin:0 0 8px;font-size:16px;\">Click anywhere to leave feedback</h3>\n <p style=\"margin:0;color:#666;font-size:14px;\">Drop numbered pins on elements you want to comment on</p>\n `\n this.shadowRoot.appendChild(this.promptEl)\n\n // Auto-dismiss after 5 seconds\n setTimeout(() => this.dismissPrompt(), 5000)\n }\n\n private dismissPrompt(): void {\n if (this.promptEl) {\n this.promptEl.remove()\n this.promptEl = null\n }\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n this.updateToolbar()\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private updateToolbar(): void {\n if (!this.toolbarEl) return\n const pinCount = this.nextPinNumber - 1\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">${this.initData.session.name}</span>\n <span style=\"opacity:0.7;\">${pinCount} pin${pinCount !== 1 ? 's' : ''}</span>\n <button class=\"review-btn review-btn--submit\" style=\"margin-left:auto;padding:6px 12px;font-size:13px;\">General Comment</button>\n `\n this.toolbarEl.querySelector('button')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleGeneralComment()\n })\n }\n\n private async loadExistingPins(): Promise<void> {\n try {\n const comments = await fetchComments(this.token)\n for (const c of comments) {\n if (c.pinNumber != null && c.pinData) {\n // commentText being non-null means it's the current reviewer's pin\n const isMine = c.commentText != null\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine)\n }\n }\n } catch {\n // Silently fail - pins just won't show up\n }\n }\n\n private handleClick(e: MouseEvent): void {\n // Don't intercept clicks on our own shadow DOM elements\n const path = e.composedPath()\n if (path.some((el) => el instanceof HTMLElement && el.closest?.('#ourroadmaps-review'))) return\n\n // Don't intercept if comment card is open\n if (this.pendingPinNumber != null) return\n\n // Dismiss first-visit prompt on first click\n this.dismissPrompt()\n\n const pinData = captureElementContext(e.clientX, e.clientY)\n const pinNumber = this.nextPinNumber\n\n // Add pin at click position\n this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true)\n this.pendingPinNumber = pinNumber\n\n // Show comment card\n this.commentCard.show({\n x: e.clientX,\n y: e.clientY,\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber,\n pinData,\n pageUrl: window.location.pathname,\n })\n this.pendingPinNumber = null\n this.nextPinNumber++\n this.updateToolbar()\n this.showToast('Comment saved')\n },\n onCancel: () => {\n // Remove the pending pin\n this.pinManager.removePin(pinNumber)\n this.pendingPinNumber = null\n },\n })\n\n e.preventDefault()\n e.stopPropagation()\n }\n\n private handleGeneralComment(): void {\n if (this.pendingPinNumber != null) return\n\n this.commentCard.showForGeneral({\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber: null,\n pinData: null,\n pageUrl: window.location.pathname,\n })\n this.showToast('Comment saved')\n },\n onCancel: () => {},\n })\n }\n\n private showToast(message: string): void {\n const toast = document.createElement('div')\n toast.className = 'review-toast'\n toast.textContent = message\n this.shadowRoot.appendChild(toast)\n setTimeout(() => toast.remove(), 2500)\n }\n\n destroy(): void {\n document.body.style.cursor = ''\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n }\n this.dismissPrompt()\n this.toolbarEl?.remove()\n this.commentCard.hide()\n this.pinManager.destroy()\n }\n}\n","import { fetchComments } from './api'\nimport { PinManager } from './PinManager'\nimport type { ReviewComment } from './types'\n\nexport class TriageMode {\n private pinManager: PinManager\n private toolbarEl: HTMLElement | null = null\n private tooltipEl: HTMLElement | null = null\n private comments: ReviewComment[] = []\n private pinClickHandler: ((e: MouseEvent) => void) | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n ) {\n this.pinManager = new PinManager()\n }\n\n async init(): Promise<void> {\n this.pinManager.mount()\n\n // Fetch all comments\n try {\n this.comments = await fetchComments(this.token)\n for (const c of this.comments) {\n if (c.pinNumber != null && c.pinData) {\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true)\n }\n }\n } catch {\n // Show error state\n }\n\n this.renderToolbar()\n\n // Listen for clicks on pins\n this.pinClickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.classList?.contains('review-pin')) {\n const num = Number(target.dataset.pinNumber)\n if (num) this.handlePinClick(num, e)\n }\n }\n document.addEventListener('click', this.pinClickHandler, true)\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n const count = this.comments.filter((c) => c.pinNumber != null).length\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">Triage Mode</span>\n <span style=\"opacity:0.7;\">${count} pin${count !== 1 ? 's' : ''}</span>\n `\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private handlePinClick(pinNumber: number, e: MouseEvent): void {\n this.hideTooltip()\n this.pinManager.highlightPin(pinNumber)\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment) return\n\n this.tooltipEl = document.createElement('div')\n this.tooltipEl.className = 'review-tooltip'\n\n const time = new Date(comment.createdAt).toLocaleString()\n this.tooltipEl.innerHTML = `\n <div style=\"font-weight:500;margin-bottom:4px;\">Pin #${pinNumber}</div>\n <div style=\"margin-bottom:4px;\">${comment.commentText || '(no text)'}</div>\n <div style=\"font-size:11px;opacity:0.7;\">${time}</div>\n `\n\n // Position near pin\n this.tooltipEl.style.position = 'fixed'\n this.tooltipEl.style.left = `${Math.min(e.clientX + 16, window.innerWidth - 300)}px`\n this.tooltipEl.style.top = `${Math.min(e.clientY - 10, window.innerHeight - 150)}px`\n this.shadowRoot.appendChild(this.tooltipEl)\n\n // Click elsewhere to dismiss\n const dismiss = (ev: MouseEvent) => {\n if (ev.target !== this.tooltipEl && !this.tooltipEl?.contains(ev.target as Node)) {\n this.hideTooltip()\n this.pinManager.clearHighlight()\n document.removeEventListener('click', dismiss, true)\n }\n }\n setTimeout(() => document.addEventListener('click', dismiss, true), 0)\n }\n\n private hideTooltip(): void {\n this.tooltipEl?.remove()\n this.tooltipEl = null\n }\n\n destroy(): void {\n if (this.pinClickHandler) {\n document.removeEventListener('click', this.pinClickHandler, true)\n }\n this.hideTooltip()\n this.toolbarEl?.remove()\n this.pinManager.destroy()\n }\n}\n","import { validateToken, ReviewError } from './api'\nimport { ReviewMode } from './ReviewMode'\nimport { TriageMode } from './TriageMode'\nimport { REVIEW_STYLES } from './styles'\nimport type { ReviewOptions } from './types'\n\nexport class Review {\n private root: HTMLElement\n private shadow: ShadowRoot\n private mode: ReviewMode | TriageMode | null = null\n private _isDestroyed = false\n\n constructor(_options: ReviewOptions = {}) {\n this.root = document.createElement('div')\n this.root.id = 'ourroadmaps-review'\n this.shadow = this.root.attachShadow({ mode: 'open' })\n\n const styleEl = document.createElement('style')\n styleEl.textContent = REVIEW_STYLES\n this.shadow.appendChild(styleEl)\n\n document.body.appendChild(this.root)\n }\n\n async init(): Promise<void> {\n const params = new URLSearchParams(window.location.search)\n const reviewToken = params.get('review')\n const triageToken = params.get('triage')\n\n if (reviewToken) {\n try {\n const data = await validateToken(reviewToken)\n this.mode = new ReviewMode(reviewToken, this.shadow, data)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n } else if (triageToken) {\n try {\n await validateToken(triageToken)\n this.mode = new TriageMode(triageToken, this.shadow)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n }\n }\n\n private showErrorOverlay(message: string): void {\n const overlay = document.createElement('div')\n overlay.className = 'review-expired-overlay'\n overlay.innerHTML = `\n <div class=\"review-expired-card\">\n <h2 style=\"margin:0 0 8px;font-size:18px;\">Session Unavailable</h2>\n <p style=\"margin:0;color:#666;font-size:14px;\">${message}</p>\n <p style=\"margin:12px 0 0;color:#999;font-size:13px;\">Contact the prototype owner for a new link.</p>\n </div>\n `\n this.shadow.appendChild(overlay)\n\n // Apply grayscale to body\n document.body.style.filter = 'grayscale(0.8)'\n }\n\n destroy(): void {\n if (this._isDestroyed) return\n this._isDestroyed = true\n this.mode?.destroy()\n this.root.remove()\n document.body.style.filter = ''\n }\n}\n"]}