@@ -5,7 +5,12 @@ window.ProbeRender = (function () {
55 var FAIL_BG = '#cf222e' ;
66 var SKIP_BG = '#656d76' ;
77 var EXPECT_BG = '#444c56' ;
8- var pillCss = 'text-align:center;padding:2px 4px;font-size:11px;font-weight:600;color:#fff;border-radius:3px;min-width:28px;display:inline-block;line-height:18px;' ;
8+ var pillCss = 'text-align:center;padding:2px 4px;font-size:11px;font-weight:600;color:#fff;border-radius:3px;min-width:28px;display:inline-block;line-height:18px;cursor:default;' ;
9+
10+ function escapeAttr ( s ) {
11+ if ( ! s ) return '' ;
12+ return s . replace ( / & / g, '&' ) . replace ( / " / g, '"' ) . replace ( / < / g, '<' ) . replace ( / > / g, '>' ) ;
13+ }
914
1015 // Servers temporarily hidden from results (undergoing major changes)
1116 var BLACKLISTED_SERVERS = [ 'GenHTTP' ] ;
@@ -44,10 +49,100 @@ window.ProbeRender = (function () {
4449 + 'html.dark .probe-table tbody tr{border-bottom-color:#30363d}'
4550 + 'html.dark .probe-server-row:hover{background:#161b22}'
4651 + 'html.dark .probe-server-row.probe-row-active{background:#2a3a50 !important}'
47- + 'html.dark .probe-table thead a{color:#58a6ff !important}' ;
52+ + 'html.dark .probe-table thead a{color:#58a6ff !important}'
53+ // Tooltip (hover)
54+ + '.probe-tooltip{position:fixed;z-index:9999;background:#1c1c1c;color:#e0e0e0;font-family:monospace;font-size:11px;'
55+ + 'white-space:pre;padding:8px 10px;border-radius:6px;max-width:500px;max-height:300px;overflow:auto;'
56+ + 'pointer-events:none;box-shadow:0 4px 16px rgba(0,0,0,0.3);line-height:1.4}'
57+ + '.probe-tooltip .probe-note{color:#f0c674;font-family:sans-serif;font-weight:600;font-size:11px;margin-bottom:6px;white-space:normal}'
58+ + '.probe-tooltip .probe-label{color:#81a2be;font-family:sans-serif;font-weight:700;font-size:10px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:2px}'
59+ + '.probe-tooltip .probe-label:not(:first-child){margin-top:8px;padding-top:8px;border-top:1px solid #333}'
60+ // Modal (click)
61+ + '.probe-modal-overlay{position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center}'
62+ + '.probe-modal{background:#1c1c1c;color:#e0e0e0;font-family:monospace;font-size:12px;white-space:pre;'
63+ + 'padding:16px 20px;border-radius:8px;max-width:700px;max-height:80vh;overflow:auto;'
64+ + 'box-shadow:0 8px 32px rgba(0,0,0,0.5);line-height:1.5;position:relative;min-width:300px}'
65+ + '.probe-modal .probe-note{color:#f0c674;font-family:sans-serif;font-weight:600;font-size:13px;margin-bottom:10px;white-space:normal}'
66+ + '.probe-modal .probe-label{color:#81a2be;font-family:sans-serif;font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:4px}'
67+ + '.probe-modal .probe-label:not(:first-child){margin-top:12px;padding-top:12px;border-top:1px solid #333}'
68+ + '.probe-modal-close{position:sticky;top:0;float:right;background:none;border:none;color:#808080;font-size:20px;'
69+ + 'cursor:pointer;padding:0 4px;line-height:1;font-family:sans-serif}'
70+ + '.probe-modal-close:hover{color:#fff}' ;
4871 var style = document . createElement ( 'style' ) ;
4972 style . textContent = css ;
5073 document . head . appendChild ( style ) ;
74+
75+ // Tooltip hover handler (delegated)
76+ var tip = null ;
77+ document . addEventListener ( 'mouseover' , function ( e ) {
78+ var target = e . target . closest ( '[data-tooltip]' ) ;
79+ if ( ! target ) return ;
80+ if ( tip ) { tip . remove ( ) ; tip = null ; }
81+ var text = target . getAttribute ( 'data-tooltip' ) ;
82+ if ( ! text ) return ;
83+ tip = document . createElement ( 'div' ) ;
84+ tip . className = 'probe-tooltip' ;
85+ var note = target . getAttribute ( 'data-note' ) ;
86+ var req = target . getAttribute ( 'data-request' ) ;
87+ var html = '' ;
88+ if ( note ) html += '<div class="probe-note">' + escapeAttr ( note ) + '</div>' ;
89+ if ( req ) html += '<div class="probe-label">Request</div>' + escapeAttr ( req ) ;
90+ if ( text ) html += '<div class="probe-label">Response</div>' + escapeAttr ( text ) ;
91+ tip . innerHTML = html ;
92+ document . body . appendChild ( tip ) ;
93+ var rect = target . getBoundingClientRect ( ) ;
94+ var tipRect = tip . getBoundingClientRect ( ) ;
95+ var left = rect . left + rect . width / 2 - tipRect . width / 2 ;
96+ if ( left < 4 ) left = 4 ;
97+ if ( left + tipRect . width > window . innerWidth - 4 ) left = window . innerWidth - 4 - tipRect . width ;
98+ var top = rect . top - tipRect . height - 6 ;
99+ if ( top < 4 ) top = rect . bottom + 6 ;
100+ tip . style . left = left + 'px' ;
101+ tip . style . top = top + 'px' ;
102+ } ) ;
103+ document . addEventListener ( 'mouseout' , function ( e ) {
104+ var target = e . target . closest ( '[data-tooltip]' ) ;
105+ if ( target && tip ) { tip . remove ( ) ; tip = null ; }
106+ } ) ;
107+
108+ // Modal click handler (delegated)
109+ document . addEventListener ( 'click' , function ( e ) {
110+ var target = e . target . closest ( '[data-tooltip]' ) ;
111+ if ( ! target ) return ;
112+ var text = target . getAttribute ( 'data-tooltip' ) ;
113+ var req = target . getAttribute ( 'data-request' ) ;
114+ if ( ! text && ! req ) return ;
115+ // Dismiss hover tooltip
116+ if ( tip ) { tip . remove ( ) ; tip = null ; }
117+
118+ var note = target . getAttribute ( 'data-note' ) ;
119+ var html = '<button class="probe-modal-close" title="Close">×</button>' ;
120+ if ( note ) html += '<div class="probe-note">' + escapeAttr ( note ) + '</div>' ;
121+ if ( req ) html += '<div class="probe-label">Request</div>' + escapeAttr ( req ) ;
122+ if ( text ) html += '<div class="probe-label">Response</div>' + escapeAttr ( text ) ;
123+
124+ var overlay = document . createElement ( 'div' ) ;
125+ overlay . className = 'probe-modal-overlay' ;
126+ var modal = document . createElement ( 'div' ) ;
127+ modal . className = 'probe-modal' ;
128+ modal . innerHTML = html ;
129+ overlay . appendChild ( modal ) ;
130+ document . body . appendChild ( overlay ) ;
131+
132+ // Close on X button
133+ modal . querySelector ( '.probe-modal-close' ) . addEventListener ( 'click' , function ( ) {
134+ overlay . remove ( ) ;
135+ } ) ;
136+ // Close on overlay click (outside modal)
137+ overlay . addEventListener ( 'click' , function ( ev ) {
138+ if ( ev . target === overlay ) overlay . remove ( ) ;
139+ } ) ;
140+ // Close on Escape
141+ function onKey ( ev ) {
142+ if ( ev . key === 'Escape' ) { overlay . remove ( ) ; document . removeEventListener ( 'keydown' , onKey ) ; }
143+ }
144+ document . addEventListener ( 'keydown' , onKey ) ;
145+ } ) ;
51146 }
52147
53148 // ── Test ID → doc page URL mapping ─────────────────────────────
@@ -197,8 +292,14 @@ window.ProbeRender = (function () {
197292 return TEST_URLS [ tid ] || '' ;
198293 }
199294
200- function pill ( bg , label ) {
201- return '<span style="' + pillCss + 'background:' + bg + ';">' + label + '</span>' ;
295+ function pill ( bg , label , tooltipRaw , tooltipNote , tooltipReq ) {
296+ var extra = '' ;
297+ var hasData = tooltipRaw || tooltipReq ;
298+ if ( hasData ) extra += ' data-tooltip="' + escapeAttr ( tooltipRaw || '' ) + '"' ;
299+ if ( tooltipNote ) extra += ' data-note="' + escapeAttr ( tooltipNote ) + '"' ;
300+ if ( tooltipReq ) extra += ' data-request="' + escapeAttr ( tooltipReq ) + '"' ;
301+ var cursor = hasData ? 'cursor:pointer;' : 'cursor:default;' ;
302+ return '<span style="' + pillCss + cursor + 'background:' + bg + ';"' + extra + '>' + label + '</span>' ;
202303 }
203304
204305 function verdictBg ( v ) {
@@ -396,7 +497,7 @@ window.ProbeRender = (function () {
396497 t += '<td style="text-align:center;padding:2px 3px;' + opacity + '">' + pill ( SKIP_BG , '\u2014' ) + '</td>' ;
397498 return ;
398499 }
399- t += '<td style="text-align:center;padding:2px 3px;' + opacity + '">' + pill ( verdictBg ( r . verdict ) , r . got ) + '</td>' ;
500+ t += '<td style="text-align:center;padding:2px 3px;' + opacity + '">' + pill ( verdictBg ( r . verdict ) , r . got , r . rawResponse , r . behavioralNote , r . rawRequest ) + '</td>' ;
400501 } ) ;
401502 t += '</tr>' ;
402503 } ) ;
0 commit comments