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: #1500ffff;
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+ }
58+
59+ /* Active State */
60+ .ai-fab.active {
61+ background: #ff0000ff;
62+ animation: pulse 2s infinite;
63+ }
64+
65+ /* Hover Effect */
66+ .container:hover .ai-fab {
67+ transform: scale(1.05);
68+ }
69+
70+ /* Close Button */
71+ .close-btn {
72+ position: absolute;
73+ top: -8px;
74+ right: -8px;
75+ width: 20px;
76+ height: 20px;
77+ background: #4b5563;
78+ color: #fff;
79+ border-radius: 50%;
80+ display: flex;
81+ align-items: center;
82+ justify-content: center;
83+ font-size: 12px;
84+ font-weight: bold;
85+ cursor: pointer;
86+ opacity: 0; /* Hidden by default */
87+ transition: opacity 0.2s;
88+ border: 2px solid white;
89+ }
90+
91+ /* close button on hover */
92+ .container:hover .close-btn {
93+ opacity: 1;
94+ }
95+
96+ @keyframes pulse {
97+ 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); }
98+ 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); }
99+ 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); }
100+ }
101+ ` ;
102+ shadow . appendChild ( style ) ;
103+
104+ const container = document . createElement ( 'div' ) ;
105+ container . className = 'container' ;
106+
107+ // main button
108+ const btn = document . createElement ( 'div' ) ;
109+ btn . className = 'ai-fab' ;
110+ const btnImg = document . createElement ( 'img' ) ;
111+ btnImg . src = 'zeuz.png'
112+ btn . innerHTML = btnImg ;
113+
114+ // close btn
115+ const closeBtn = document . createElement ( 'div' ) ;
116+ closeBtn . className = 'close-btn' ;
117+ closeBtn . innerHTML = '✕' ;
118+
119+ closeBtn . addEventListener ( 'click' , ( e ) => {
120+ e . stopPropagation ( ) ;
121+ host . remove ( ) ; // remove the whole UI
122+ } ) ;
123+
124+ container . appendChild ( btn ) ;
125+ container . appendChild ( closeBtn ) ;
126+ shadow . appendChild ( container ) ;
127+
128+ // drag
129+ let isDragging = false ;
130+ let hasMoved = false ;
131+ let startX , startY , initialRight , initialBottom ;
132+
133+ const onMouseDown = ( e ) => {
134+ isDragging = true ;
135+ hasMoved = false ;
136+
137+ const rect = host . getBoundingClientRect ( ) ;
138+
139+ host . style . right = 'auto' ;
140+ host . style . bottom = 'auto' ;
141+ host . style . left = rect . left + 'px' ;
142+ host . style . top = rect . top + 'px' ;
143+
144+ startX = e . clientX ;
145+ startY = e . clientY ;
146+ } ;
147+
148+ const onMouseMove = ( e ) => {
149+ if ( ! isDragging ) return ;
150+
151+ const dx = e . clientX - startX ;
152+ const dy = e . clientY - startY ;
153+
154+ if ( Math . abs ( dx ) > 3 || Math . abs ( dy ) > 3 ) {
155+ hasMoved = true ;
156+ }
157+
158+ host . style . left = ( host . offsetLeft + dx ) + 'px' ;
159+ host . style . top = ( host . offsetTop + dy ) + 'px' ;
160+
161+ startX = e . clientX ;
162+ startY = e . clientY ;
163+ } ;
164+
165+ const onMouseUp = ( ) => {
166+ isDragging = false ;
167+ } ;
168+
169+ // drag listeners
170+ container . addEventListener ( 'mousedown' , onMouseDown ) ;
171+ window . addEventListener ( 'mousemove' , onMouseMove ) ;
172+ window . addEventListener ( 'mouseup' , onMouseUp ) ;
173+
174+ btn . addEventListener ( 'click' , ( ) => {
175+ if ( ! hasMoved ) {
176+ chrome . runtime . sendMessage ( { action : 'toggle_from_content_script' } ) ;
177+ }
178+ } ) ;
179+
180+ chrome . runtime . onMessage . addListener ( ( request ) => {
181+ if ( request . action === 'activate' ) {
182+ btn . classList . add ( 'active' ) ;
183+ } else if ( request . action === 'deactivate' ) {
184+ btn . classList . remove ( 'active' ) ;
185+ }
186+ } ) ;
187+ }
188+
189+ injectInspectorUI ( ) ;
0 commit comments