@@ -496,6 +496,8 @@ document.addEventListener('DOMContentLoaded', () => {
496496 loadStoredFile ( ) ;
497497 }
498498 } ) ;
499+
500+ setupTooltips ( ) ;
499501 }
500502
501503 // ==================
@@ -1019,4 +1021,98 @@ document.addEventListener('DOMContentLoaded', () => {
10191021 elements . sendBtn . disabled = false ;
10201022 }
10211023 }
1024+ function setupTooltips ( ) {
1025+ const tooltip = document . getElementById ( 'tooltip' ) ;
1026+ let activeElement = null ;
1027+ let tooltipTimeout = null ;
1028+
1029+ document . addEventListener ( 'mouseover' , ( e ) => {
1030+ // Find closest element with a title attribute or data-tooltip
1031+ const target = e . target . closest ( '[title], [data-tooltip]' ) ;
1032+
1033+ // If we are already tracking this element, do nothing (prevents timer reset on mouse move inside element)
1034+ if ( activeElement && target === activeElement ) {
1035+ return ;
1036+ }
1037+
1038+ // If we moved to a new element (or no element), clear any pending tooltip
1039+ if ( activeElement ) {
1040+ clearTimeout ( tooltipTimeout ) ;
1041+ hideTooltip ( ) ;
1042+ }
1043+
1044+ if ( ! target ) {
1045+ return ;
1046+ }
1047+
1048+ // If it has a title, move it to data-tooltip to suppress native tooltip
1049+ if ( target . hasAttribute ( 'title' ) ) {
1050+ const title = target . getAttribute ( 'title' ) ;
1051+ target . setAttribute ( 'data-tooltip' , title ) ;
1052+ target . removeAttribute ( 'title' ) ;
1053+ }
1054+
1055+ const text = target . getAttribute ( 'data-tooltip' ) ;
1056+ if ( ! text ) return ;
1057+
1058+ activeElement = target ;
1059+
1060+ // Delay showing the tooltip
1061+ tooltipTimeout = setTimeout ( ( ) => {
1062+ // Double-check we are still active on this element
1063+ if ( activeElement === target ) {
1064+ showTooltip ( target , text ) ;
1065+ }
1066+ } , 1000 ) ; // 1000ms delay
1067+ } ) ;
1068+
1069+ document . addEventListener ( 'mouseout' , ( e ) => {
1070+ if ( activeElement && ( e . target === activeElement || e . target . closest ( '[data-tooltip]' ) === activeElement ) ) {
1071+ // check if we moved to child of the active element
1072+ if ( e . relatedTarget && activeElement . contains ( e . relatedTarget ) ) {
1073+ return ;
1074+ }
1075+
1076+ // otherwise, we left the element entirely
1077+ clearTimeout ( tooltipTimeout ) ;
1078+ hideTooltip ( ) ;
1079+ }
1080+ } ) ;
1081+
1082+ function showTooltip ( element , text ) {
1083+ if ( ! element . isConnected ) return ; // Verify element is still in DOM
1084+
1085+ tooltip . textContent = text ;
1086+ tooltip . classList . add ( 'visible' ) ;
1087+
1088+ const rect = element . getBoundingClientRect ( ) ;
1089+ const tooltipRect = tooltip . getBoundingClientRect ( ) ;
1090+ const margin = 8 ;
1091+
1092+ // Default: Top center
1093+ let top = rect . top - tooltipRect . height - margin ;
1094+ let left = rect . left + ( rect . width - tooltipRect . width ) / 2 ;
1095+
1096+ // prevent overflow top
1097+ if ( top < 0 ) {
1098+ top = rect . bottom + margin ;
1099+ }
1100+
1101+ // prevent overflow left/right
1102+ if ( left < margin ) {
1103+ left = margin ;
1104+ } else if ( left + tooltipRect . width > window . innerWidth - margin ) {
1105+ left = window . innerWidth - tooltipRect . width - margin ;
1106+ }
1107+
1108+ tooltip . style . top = `${ top } px` ;
1109+ tooltip . style . left = `${ left } px` ;
1110+ }
1111+
1112+ function hideTooltip ( ) {
1113+ tooltip . classList . remove ( 'visible' ) ;
1114+ activeElement = null ;
1115+ }
1116+ }
1117+
10221118} ) ;
0 commit comments