@@ -25,6 +25,14 @@ window.addEventListener('message', (event) => {
2525 handleSnapshotRequest ( event . data ) ;
2626 break ;
2727
28+ case 'SENTIENCE_SHOW_OVERLAY' :
29+ handleShowOverlay ( event . data ) ;
30+ break ;
31+
32+ case 'SENTIENCE_CLEAR_OVERLAY' :
33+ handleClearOverlay ( ) ;
34+ break ;
35+
2836 default :
2937 // Ignore unknown message types
3038 break ;
@@ -135,4 +143,156 @@ function handleSnapshotRequest(data) {
135143 }
136144}
137145
146+ // ============================================================================
147+ // Visual Overlay - Shadow DOM Implementation
148+ // ============================================================================
149+
150+ const OVERLAY_HOST_ID = 'sentience-overlay-host' ;
151+ let overlayTimeout = null ;
152+
153+ /**
154+ * Show visual overlay highlighting elements using Shadow DOM
155+ * @param {Object } data - Message data with elements and targetElementId
156+ */
157+ function handleShowOverlay ( data ) {
158+ const { elements, targetElementId } = data ;
159+
160+ if ( ! elements || ! Array . isArray ( elements ) ) {
161+ console . warn ( '[Sentience Bridge] showOverlay: elements must be an array' ) ;
162+ return ;
163+ }
164+
165+ removeOverlay ( ) ;
166+
167+ // Create host with Shadow DOM for CSS isolation
168+ const host = document . createElement ( 'div' ) ;
169+ host . id = OVERLAY_HOST_ID ;
170+ host . style . cssText = `
171+ position: fixed !important;
172+ top: 0 !important;
173+ left: 0 !important;
174+ width: 100vw !important;
175+ height: 100vh !important;
176+ pointer-events: none !important;
177+ z-index: 2147483647 !important;
178+ margin: 0 !important;
179+ padding: 0 !important;
180+ ` ;
181+ document . body . appendChild ( host ) ;
182+
183+ // Attach shadow root (closed mode for security and CSS isolation)
184+ const shadow = host . attachShadow ( { mode : 'closed' } ) ;
185+
186+ // Calculate max importance for scaling
187+ const maxImportance = Math . max ( ...elements . map ( e => e . importance || 0 ) , 1 ) ;
188+
189+ elements . forEach ( ( element ) => {
190+ const bbox = element . bbox ;
191+ if ( ! bbox ) return ;
192+
193+ const isTarget = element . id === targetElementId ;
194+ const isPrimary = element . visual_cues ?. is_primary || false ;
195+ const importance = element . importance || 0 ;
196+
197+ // Color: Red (target), Blue (primary), Green (regular)
198+ let color ;
199+ if ( isTarget ) color = '#FF0000' ;
200+ else if ( isPrimary ) color = '#0066FF' ;
201+ else color = '#00FF00' ;
202+
203+ // Scale opacity and border width based on importance
204+ const importanceRatio = maxImportance > 0 ? importance / maxImportance : 0.5 ;
205+ const borderOpacity = isTarget ? 1.0 : ( isPrimary ? 0.9 : Math . max ( 0.4 , 0.5 + importanceRatio * 0.5 ) ) ;
206+ const fillOpacity = borderOpacity * 0.2 ;
207+ const borderWidth = isTarget ? 2 : ( isPrimary ? 1.5 : Math . max ( 0.5 , Math . round ( importanceRatio * 2 ) ) ) ;
208+
209+ // Convert fill opacity to hex for background-color
210+ const hexOpacity = Math . round ( fillOpacity * 255 ) . toString ( 16 ) . padStart ( 2 , '0' ) ;
211+
212+ // Create box with semi-transparent fill
213+ const box = document . createElement ( 'div' ) ;
214+ box . style . cssText = `
215+ position: absolute;
216+ left: ${ bbox . x } px;
217+ top: ${ bbox . y } px;
218+ width: ${ bbox . width } px;
219+ height: ${ bbox . height } px;
220+ border: ${ borderWidth } px solid ${ color } ;
221+ background-color: ${ color } ${ hexOpacity } ;
222+ box-sizing: border-box;
223+ opacity: ${ borderOpacity } ;
224+ pointer-events: none;
225+ ` ;
226+
227+ // Add badge showing importance score
228+ if ( importance > 0 || isPrimary ) {
229+ const badge = document . createElement ( 'span' ) ;
230+ badge . textContent = isPrimary ? `⭐${ importance } ` : `${ importance } ` ;
231+ badge . style . cssText = `
232+ position: absolute;
233+ top: -18px;
234+ left: 0;
235+ background: ${ color } ;
236+ color: white;
237+ font-size: 11px;
238+ font-weight: bold;
239+ padding: 2px 6px;
240+ font-family: Arial, sans-serif;
241+ border-radius: 3px;
242+ opacity: 0.95;
243+ white-space: nowrap;
244+ pointer-events: none;
245+ ` ;
246+ box . appendChild ( badge ) ;
247+ }
248+
249+ // Add target emoji for target element
250+ if ( isTarget ) {
251+ const targetIndicator = document . createElement ( 'span' ) ;
252+ targetIndicator . textContent = '🎯' ;
253+ targetIndicator . style . cssText = `
254+ position: absolute;
255+ top: -18px;
256+ right: 0;
257+ font-size: 16px;
258+ pointer-events: none;
259+ ` ;
260+ box . appendChild ( targetIndicator ) ;
261+ }
262+
263+ shadow . appendChild ( box ) ;
264+ } ) ;
265+
266+ console . log ( `[Sentience Bridge] Overlay shown for ${ elements . length } elements` ) ;
267+
268+ // Auto-remove after 5 seconds
269+ overlayTimeout = setTimeout ( ( ) => {
270+ removeOverlay ( ) ;
271+ console . log ( '[Sentience Bridge] Overlay auto-cleared after 5 seconds' ) ;
272+ } , 5000 ) ;
273+ }
274+
275+ /**
276+ * Clear overlay manually
277+ */
278+ function handleClearOverlay ( ) {
279+ removeOverlay ( ) ;
280+ console . log ( '[Sentience Bridge] Overlay cleared manually' ) ;
281+ }
282+
283+ /**
284+ * Remove overlay from DOM
285+ */
286+ function removeOverlay ( ) {
287+ const existing = document . getElementById ( OVERLAY_HOST_ID ) ;
288+ if ( existing ) {
289+ existing . remove ( ) ;
290+ }
291+
292+ if ( overlayTimeout ) {
293+ clearTimeout ( overlayTimeout ) ;
294+ overlayTimeout = null ;
295+ }
296+ }
297+
138298// console.log('[Sentience Bridge] Ready - Extension ID:', chrome.runtime.id);
0 commit comments