1+ function injectInspectorUI ( ) {
2+ // headless check
3+ if ( / H e a d l e s s / i. test ( navigator . userAgent ) ) {
4+ console . log ( "Headless mode detected. ZeuZ AI Inspector UI skipped." ) ;
5+ return ;
6+ }
7+
8+ const host = document . createElement ( 'div' ) ;
9+ host . id = 'zeuz-ai-inspector-host' ;
10+
11+ // initial position (fixed)
12+ Object . assign ( host . style , {
13+ position : 'fixed' ,
14+ bottom : '20px' ,
15+ right : '20px' ,
16+ zIndex : '2147483647' , // maximum z-index
17+ width : 'auto' ,
18+ height : 'auto' ,
19+ filter : 'drop-shadow(0 4px 6px rgba(0,0,0,0.15))'
20+ } ) ;
21+
22+ document . body . appendChild ( host ) ;
23+
24+ // shadow dom
25+ const shadow = host . attachShadow ( { mode : 'open' } ) ;
26+
27+ const style = document . createElement ( 'style' ) ;
28+ style . textContent = `
29+ :host {
30+ font-family: sans-serif;
31+ }
32+ .container {
33+ position: relative;
34+ display: flex;
35+ flex-direction: column;
36+ align-items: center;
37+ cursor: grab; /* Cursor indicates draggable */
38+ user-select: none;
39+ }
40+ .container:active {
41+ cursor: grabbing;
42+ }
43+
44+ /* The Main Button */
45+ .ai-fab {
46+ width: 56px;
47+ height: 56px;
48+ background: #d3a8ffff;
49+ border-radius: 50%;
50+ border: 2px solid #fff;
51+ display: flex;
52+ align-items: center;
53+ justify-content: center;
54+ font-size: 28px;
55+ color: white;
56+ transition: all 0.2s ease;
57+ cursor: pointer;
58+ }
59+
60+ .ai-fab img {
61+ pointer-events: none;
62+ }
63+
64+ /* Active State */
65+ .ai-fab.active {
66+ background: #ff8d8dff;
67+ animation: pulse 2s infinite;
68+ cursor: default;
69+ }
70+
71+ /* Hover Effect */
72+ .container:hover .ai-fab {
73+ transform: scale(1.05);
74+ }
75+
76+ /* Close Button */
77+ .close-btn {
78+ position: absolute;
79+ top: -8px;
80+ right: -8px;
81+ width: 20px;
82+ height: 20px;
83+ background: #4b5563;
84+ color: #fff;
85+ border-radius: 50%;
86+ display: flex;
87+ align-items: center;
88+ justify-content: center;
89+ font-size: 12px;
90+ font-weight: bold;
91+ cursor: pointer;
92+ opacity: 0; /* Hidden by default */
93+ transition: opacity 0.2s;
94+ border: 2px solid white;
95+ }
96+
97+ /* close button on hover */
98+ .container:hover .close-btn {
99+ opacity: 1;
100+ }
101+
102+ @keyframes pulse {
103+ 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); }
104+ 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); }
105+ 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); }
106+ }
107+ ` ;
108+ shadow . appendChild ( style ) ;
109+
110+ const container = document . createElement ( 'div' ) ;
111+ container . className = 'container' ;
112+
113+ // main button
114+ const btn = document . createElement ( 'div' ) ;
115+ btn . className = 'ai-fab' ;
116+ const btnImg = document . createElement ( 'img' ) ;
117+ btnImg . src = chrome . runtime . getURL ( 'zeuz.png' ) ;
118+ btnImg . style . width = '32px' ;
119+ btnImg . style . height = '32px' ;
120+ btn . appendChild ( btnImg ) ;
121+
122+ // close btn
123+ const closeBtn = document . createElement ( 'div' ) ;
124+ closeBtn . className = 'close-btn' ;
125+ closeBtn . innerHTML = '✕' ;
126+
127+ closeBtn . addEventListener ( 'click' , ( e ) => {
128+ e . stopPropagation ( ) ;
129+ host . remove ( ) ; // remove the whole UI
130+ } ) ;
131+
132+ container . appendChild ( btn ) ;
133+ container . appendChild ( closeBtn ) ;
134+ shadow . appendChild ( container ) ;
135+
136+ // drag
137+ let isDragging = false ;
138+ let hasMoved = false ;
139+ let startX , startY ;
140+
141+ const onMouseDown = ( e ) => {
142+ // don't drag if inspector is active
143+ if ( btn . classList . contains ( 'active' ) ) return ;
144+
145+ isDragging = true ;
146+ hasMoved = false ;
147+
148+ const rect = host . getBoundingClientRect ( ) ;
149+
150+ host . style . right = 'auto' ;
151+ host . style . bottom = 'auto' ;
152+ host . style . left = rect . left + 'px' ;
153+ host . style . top = rect . top + 'px' ;
154+
155+ startX = e . clientX ;
156+ startY = e . clientY ;
157+
158+ e . preventDefault ( ) ;
159+ } ;
160+
161+ const onMouseMove = ( e ) => {
162+ if ( ! isDragging ) return ;
163+
164+ const dx = e . clientX - startX ;
165+ const dy = e . clientY - startY ;
166+
167+ if ( Math . abs ( dx ) > 3 || Math . abs ( dy ) > 3 ) {
168+ hasMoved = true ;
169+ }
170+
171+ host . style . left = ( host . offsetLeft + dx ) + 'px' ;
172+ host . style . top = ( host . offsetTop + dy ) + 'px' ;
173+
174+ startX = e . clientX ;
175+ startY = e . clientY ;
176+ } ;
177+
178+ const onMouseUp = ( ) => {
179+ isDragging = false ;
180+ } ;
181+
182+ // drag listeners
183+ container . addEventListener ( 'mousedown' , onMouseDown ) ;
184+ window . addEventListener ( 'mousemove' , onMouseMove ) ;
185+ window . addEventListener ( 'mouseup' , onMouseUp ) ;
186+
187+ btn . addEventListener ( 'click' , ( e ) => {
188+ if ( ! hasMoved && ! btn . classList . contains ( 'active' ) ) {
189+ chrome . runtime . sendMessage ( { action : 'toggle_from_content_script' } ) ;
190+ }
191+ hasMoved = false ; // Reset after click
192+ } ) ;
193+
194+ // right-click context menu for deactivation when inspector is active
195+ btn . addEventListener ( 'contextmenu' , ( e ) => {
196+ e . preventDefault ( ) ;
197+ if ( btn . classList . contains ( 'active' ) ) {
198+ chrome . runtime . sendMessage ( { action : 'toggle_from_content_script' } ) ;
199+ }
200+ } ) ;
201+
202+ chrome . runtime . onMessage . addListener ( ( request ) => {
203+ if ( request . action === 'activate' ) {
204+ btn . classList . add ( 'active' ) ;
205+ btnImg . src = chrome . runtime . getURL ( 'zeuz-active.png' ) ;
206+ } else if ( request . action === 'deactivate' ) {
207+ btn . classList . remove ( 'active' ) ;
208+ btnImg . src = chrome . runtime . getURL ( 'zeuz.png' ) ;
209+ }
210+ } ) ;
211+ }
212+
213+ injectInspectorUI ( ) ;
0 commit comments