antigravity-rtl-patcher 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -5,13 +5,13 @@ const chalk = require('chalk');
5
5
  const { patch, restore, status } = require('../src/patcher');
6
6
 
7
7
  const BANNER = `
8
- ${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.2.0')}
8
+ ${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.3.0')}
9
9
  `;
10
10
 
11
11
  program
12
12
  .name('agy-rtl')
13
13
  .description('RTL patcher for Antigravity')
14
- .version('2.2.0');
14
+ .version('2.3.0');
15
15
 
16
16
  program
17
17
  .command('patch')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-rtl-patcher",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Smart RTL and Persian/Arabic typography patcher for Antigravity",
5
5
  "main": "src/patcher.js",
6
6
  "bin": {
@@ -1,17 +1,15 @@
1
1
  /**
2
2
  * AGY RTL — Preload Injection Snippet
3
3
  * Appended to Antigravity's preload.js at patch time.
4
- * Injects the RTL engine + toggle panel into every renderer page.
4
+ *
5
+ * Only applies RTL direction to individual text elements containing
6
+ * Persian/Arabic/Hebrew characters. Does NOT change the app layout.
5
7
  *
6
8
  * MARKER: __AGY_RTL_INJECTED__
7
9
  */
8
10
  (function _agyRtlPreloadInit() {
9
- // Only run in renderer context
10
11
  if (typeof document === 'undefined') {
11
- // We're in the preload context before DOM exists.
12
- // Schedule injection for when the page actually loads.
13
12
  const { webFrame } = require('electron');
14
-
15
13
  webFrame.executeJavaScript(`(${_agyRtlRendererMain.toString()})();`);
16
14
  return;
17
15
  }
@@ -21,91 +19,216 @@
21
19
  function _agyRtlRendererMain() {
22
20
  'use strict';
23
21
 
24
- // Prevent double injection
25
22
  if (window.__AGY_RTL_LOADED__) return;
26
23
  window.__AGY_RTL_LOADED__ = true;
27
24
 
28
- // ─── Configuration ───────────────────────────────────────
25
+ // ─── Config ──────────────────────────────────────────────
29
26
  var STORAGE_KEY = 'agy-rtl-enabled';
30
27
  var PANEL_ID = 'agy-rtl-panel';
31
28
  var TOGGLE_BTN_ID = 'agy-rtl-toggle-btn';
32
29
  var STATUS_ICON_ID = 'agy-rtl-status-icon';
33
30
  var GITHUB_URL = 'https://github.com/VaFa1726/antigravity-rtl-patcher';
31
+ var PROCESSED_ATTR = 'data-agy-rtl';
34
32
 
35
- // RTL detection
36
33
  var RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
37
34
  var RTL_CHAR_REGEX = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g;
38
- var MIN_RTL_RATIO = 0.3;
39
- var PROCESSED_ATTR = 'data-agy-rtl';
40
-
41
- var TARGET_SELECTORS = [
42
- 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'td', 'th',
43
- '.chat-response-content p', '.chat-response-content li',
44
- '.chat-response-content h1', '.chat-response-content h2',
45
- '.chat-response-content h3', '.chat-response-content h4',
46
- '.chat-response-content blockquote',
47
- '.markdown-body p', '.markdown-body li',
48
- '.markdown-body h1', '.markdown-body h2',
49
- '.markdown-body h3', '.markdown-body blockquote',
50
- '.rendered-markdown p', '.rendered-markdown li',
51
- '.rendered-markdown h1', '.rendered-markdown h2',
52
- '.rendered-markdown h3', '.rendered-markdown blockquote',
53
- '.monaco-hover-content p', '.suggest-details p',
54
- '.notification-toast-container p',
55
- ].join(', ');
35
+ var MIN_RTL_RATIO = 0.25;
56
36
 
57
37
  // ─── State ───────────────────────────────────────────────
58
38
  var isEnabled = getStoredState();
59
39
  var observer = null;
60
40
  var panelVisible = false;
41
+ var hoverTimeout = null;
61
42
 
62
43
  // ─── CSS ─────────────────────────────────────────────────
63
- var CSS = [
64
- '@import url("https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap");',
65
- '[data-agy-rtl="true"]{font-family:"Vazirmatn",system-ui,-apple-system,"Segoe UI",sans-serif!important;line-height:1.85!important;word-spacing:.02em}',
66
- '[data-agy-rtl="true"] code,[data-agy-rtl="true"] pre,[data-agy-rtl="true"] .monaco-tokenized-source{direction:ltr!important;text-align:left!important;unicode-bidi:isolate!important;font-family:"Cascadia Code","Fira Code","JetBrains Mono",Consolas,monospace!important;display:inline-block}',
67
- '[data-agy-rtl="true"] code{margin:0 3px;padding:1px 5px;border-radius:3px}',
68
- '[data-agy-rtl="true"] li{list-style-position:inside}',
69
- // Status icon
70
- '.agy-rtl-status-icon{position:fixed;bottom:0;right:60px;z-index:99999;display:flex;align-items:center;justify-content:center;width:32px;height:22px;cursor:pointer;color:#ccc;opacity:.85;transition:opacity .2s,color .2s,background .15s;border-radius:3px 3px 0 0;user-select:none;-webkit-user-select:none;background:rgba(0,0,0,.25)}',
71
- '.agy-rtl-status-icon:hover{opacity:1;color:#fff;background:rgba(255,255,255,.12)}',
72
- '.agy-rtl-status-dot{position:absolute;top:2px;right:2px;width:6px;height:6px;border-radius:50%;background:#666;transition:background .3s,box-shadow .3s}',
73
- '.agy-rtl-status-dot.active{background:#4caf50;box-shadow:0 0 4px rgba(76,175,80,.6)}',
74
- // Panel
75
- '.agy-rtl-panel{position:fixed;z-index:100000;min-width:260px;max-width:300px;background:#252526;border:1px solid #454545;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,.45),0 2px 8px rgba(0,0,0,.2);padding:16px;opacity:0;visibility:hidden;transform:translateY(8px) scale(.96);transition:opacity .2s,transform .2s,visibility .2s;font-family:-apple-system,"Segoe UI",system-ui,sans-serif;font-size:13px;color:#ccc}',
76
- '.agy-rtl-panel.visible{opacity:1;visibility:visible;transform:translateY(0) scale(1)}',
77
- '.agy-rtl-panel-header{margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #3a3a3a}',
78
- '.agy-rtl-panel-title{font-size:14px;font-weight:600;color:#e0e0e0;letter-spacing:-.01em}',
79
- '.agy-rtl-panel-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0}',
80
- '.agy-rtl-panel-label{font-size:13px;color:#ccc}',
81
- '.agy-rtl-panel-sep{height:1px;background:#3a3a3a;margin:8px 0}',
82
- // Toggle switch
83
- '.agy-rtl-switch{position:relative;display:inline-block;width:40px;height:22px;cursor:pointer;flex-shrink:0}',
84
- '.agy-rtl-switch input{opacity:0;width:0;height:0;position:absolute}',
85
- '.agy-rtl-slider{position:absolute;inset:0;background:#555;border-radius:22px;transition:background .25s,box-shadow .25s}',
86
- '.agy-rtl-slider::before{content:"";position:absolute;width:16px;height:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:transform .25s;box-shadow:0 1px 3px rgba(0,0,0,.3)}',
87
- '.agy-rtl-switch input:checked+.agy-rtl-slider{background:#4b9cf5;box-shadow:0 0 8px rgba(75,156,245,.3)}',
88
- '.agy-rtl-switch input:checked+.agy-rtl-slider::before{transform:translateX(18px)}',
89
- // GitHub link
90
- '.agy-rtl-panel-gh{justify-content:center;padding-top:4px}',
91
- '.agy-rtl-gh-link{display:inline-flex;align-items:center;gap:6px;color:#9cdcfe;text-decoration:none;font-size:12px;opacity:.8;transition:opacity .2s}',
92
- '.agy-rtl-gh-link:hover{opacity:1;text-decoration:underline}',
93
- ].join('\n');
44
+ var CSS = `
45
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
46
+
47
+ /* RTL styling applied per-element, NOT globally */
48
+ [${PROCESSED_ATTR}="true"] {
49
+ direction: rtl !important;
50
+ text-align: right !important;
51
+ unicode-bidi: plaintext !important;
52
+ font-family: 'Vazirmatn', system-ui, -apple-system, 'Segoe UI', sans-serif !important;
53
+ line-height: 1.8 !important;
54
+ }
55
+
56
+ /* Keep code LTR inside RTL blocks */
57
+ [${PROCESSED_ATTR}="true"] code,
58
+ [${PROCESSED_ATTR}="true"] pre {
59
+ direction: ltr !important;
60
+ text-align: left !important;
61
+ unicode-bidi: isolate !important;
62
+ font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, monospace !important;
63
+ display: inline-block;
64
+ }
65
+ [${PROCESSED_ATTR}="true"] code {
66
+ margin: 0 3px;
67
+ padding: 1px 5px;
68
+ border-radius: 3px;
69
+ }
70
+ [${PROCESSED_ATTR}="true"] li {
71
+ list-style-position: inside;
72
+ }
73
+
74
+ /* === Status Bar Icon === */
75
+ .agy-rtl-status-icon {
76
+ position: fixed;
77
+ bottom: 6px;
78
+ right: 16px;
79
+ z-index: 99999;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ gap: 6px;
84
+ height: 28px;
85
+ padding: 0 12px;
86
+ cursor: pointer;
87
+ border-radius: 14px;
88
+ user-select: none;
89
+ -webkit-user-select: none;
90
+ transition: background .2s, opacity .2s, box-shadow .2s;
91
+ opacity: .6;
92
+ font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
93
+ font-size: 12px;
94
+ font-weight: 600;
95
+ letter-spacing: .03em;
96
+ color: #888;
97
+ background: rgba(128,128,128,.08);
98
+ }
99
+ .agy-rtl-status-icon:hover {
100
+ opacity: 1;
101
+ background: rgba(128,128,128,.15);
102
+ color: #aaa;
103
+ }
104
+ .agy-rtl-status-icon.active {
105
+ color: #5ba0d6;
106
+ opacity: .85;
107
+ background: rgba(91,160,214,.08);
108
+ }
109
+ .agy-rtl-status-icon.active:hover {
110
+ opacity: 1;
111
+ background: rgba(91,160,214,.15);
112
+ box-shadow: 0 0 12px rgba(91,160,214,.12);
113
+ }
114
+ .agy-rtl-status-icon svg {
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ /* === Panel === */
119
+ .agy-rtl-panel {
120
+ position: fixed;
121
+ z-index: 100000;
122
+ width: 260px;
123
+ background: #1e1e1e;
124
+ border: 1px solid #333;
125
+ border-radius: 10px;
126
+ box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.04);
127
+ padding: 0;
128
+ opacity: 0;
129
+ visibility: hidden;
130
+ transform: translateY(6px);
131
+ transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
132
+ font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
133
+ font-size: 13px;
134
+ color: #ccc;
135
+ overflow: hidden;
136
+ direction: ltr !important;
137
+ text-align: left !important;
138
+ }
139
+ .agy-rtl-panel.visible {
140
+ opacity: 1;
141
+ visibility: visible;
142
+ transform: translateY(0);
143
+ }
144
+ .agy-rtl-panel-header {
145
+ padding: 14px 16px 12px;
146
+ background: rgba(255,255,255,.03);
147
+ border-bottom: 1px solid #2a2a2a;
148
+ }
149
+ .agy-rtl-panel-title {
150
+ font-size: 13px;
151
+ font-weight: 600;
152
+ color: #e0e0e0;
153
+ }
154
+ .agy-rtl-panel-body {
155
+ padding: 8px 16px 12px;
156
+ }
157
+ .agy-rtl-panel-row {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: space-between;
161
+ padding: 8px 0;
162
+ }
163
+ .agy-rtl-panel-label {
164
+ font-size: 13px;
165
+ color: #b0b0b0;
166
+ }
167
+
168
+ /* Toggle */
169
+ .agy-rtl-switch {
170
+ position: relative;
171
+ display: inline-block;
172
+ width: 36px;
173
+ height: 20px;
174
+ cursor: pointer;
175
+ flex-shrink: 0;
176
+ }
177
+ .agy-rtl-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
178
+ .agy-rtl-slider {
179
+ position: absolute;
180
+ inset: 0;
181
+ background: #444;
182
+ border-radius: 20px;
183
+ transition: background .2s;
184
+ }
185
+ .agy-rtl-slider::before {
186
+ content: '';
187
+ position: absolute;
188
+ width: 14px;
189
+ height: 14px;
190
+ left: 3px;
191
+ bottom: 3px;
192
+ background: #fff;
193
+ border-radius: 50%;
194
+ transition: transform .2s;
195
+ box-shadow: 0 1px 2px rgba(0,0,0,.3);
196
+ }
197
+ .agy-rtl-switch input:checked + .agy-rtl-slider {
198
+ background: #4b9cf5;
199
+ }
200
+ .agy-rtl-switch input:checked + .agy-rtl-slider::before {
201
+ transform: translateX(16px);
202
+ }
203
+
204
+ /* Footer */
205
+ .agy-rtl-panel-footer {
206
+ padding: 8px 16px;
207
+ background: rgba(255,255,255,.02);
208
+ border-top: 1px solid #2a2a2a;
209
+ text-align: center;
210
+ }
211
+ .agy-rtl-gh-link {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ gap: 5px;
215
+ color: #555;
216
+ text-decoration: none;
217
+ font-size: 11px;
218
+ transition: color .15s;
219
+ }
220
+ .agy-rtl-gh-link:hover { color: #9cdcfe; }
221
+ `;
94
222
 
95
223
  // ─── Helpers ─────────────────────────────────────────────
96
224
 
97
225
  function getStoredState() {
98
- try {
99
- var s = localStorage.getItem(STORAGE_KEY);
100
- return s === null ? true : s === 'true';
101
- } catch (e) { return true; }
226
+ try { var s = localStorage.getItem(STORAGE_KEY); return s === null ? true : s === 'true'; }
227
+ catch (e) { return true; }
102
228
  }
103
-
104
229
  function setStoredState(v) {
105
- try { localStorage.setItem(STORAGE_KEY, v ? 'true' : 'false'); }
106
- catch (e) { /* ignore */ }
230
+ try { localStorage.setItem(STORAGE_KEY, v ? 'true' : 'false'); } catch (e) {}
107
231
  }
108
-
109
232
  function isRTL(text) {
110
233
  if (!text || text.trim().length === 0) return false;
111
234
  if (!RTL_REGEX.test(text)) return false;
@@ -114,32 +237,37 @@ function _agyRtlRendererMain() {
114
237
  var alpha = text.replace(/[\s\d\W]/g, '').length;
115
238
  return alpha > 0 && (m.length / alpha) >= MIN_RTL_RATIO;
116
239
  }
117
-
118
240
  function applyRTL(el) {
119
241
  if (el.getAttribute(PROCESSED_ATTR)) return;
120
- if (isRTL(el.textContent || '')) {
242
+ // Skip the RTL panel/icon itself
243
+ if (el.closest && (el.closest('#' + PANEL_ID) || el.closest('#' + STATUS_ICON_ID))) return;
244
+ var text = el.textContent || '';
245
+ if (text.trim().length > 0 && isRTL(text)) {
121
246
  el.setAttribute(PROCESSED_ATTR, 'true');
122
- el.style.direction = 'rtl';
123
- el.style.textAlign = 'start';
124
- el.style.unicodeBidi = 'plaintext';
125
247
  }
126
248
  }
127
-
128
249
  function removeRTL(el) {
129
250
  if (el.getAttribute(PROCESSED_ATTR)) {
130
251
  el.removeAttribute(PROCESSED_ATTR);
131
- el.style.direction = '';
132
- el.style.textAlign = '';
133
- el.style.unicodeBidi = '';
134
252
  }
135
253
  }
136
254
 
255
+ // Wide selectors — catch all text-bearing elements
256
+ var SELECTORS = [
257
+ 'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
258
+ 'blockquote', 'td', 'th', 'span', 'a', 'label',
259
+ 'div', 'button',
260
+ ].join(',');
261
+
137
262
  function scanElement(root) {
138
263
  if (!root || root.nodeType !== 1) return;
139
- try { if (root.matches && root.matches(TARGET_SELECTORS)) applyRTL(root); } catch (e) {}
140
- try { root.querySelectorAll(TARGET_SELECTORS).forEach(applyRTL); } catch (e) {}
141
- }
264
+ // Skip our own UI
265
+ if (root.id === PANEL_ID || root.id === STATUS_ICON_ID) return;
266
+ if (root.closest && (root.closest('#' + PANEL_ID) || root.closest('#' + STATUS_ICON_ID))) return;
142
267
 
268
+ try { if (root.matches && root.matches(SELECTORS)) applyRTL(root); } catch (e) {}
269
+ try { root.querySelectorAll(SELECTORS).forEach(applyRTL); } catch (e) {}
270
+ }
143
271
  function clearAllRTL() {
144
272
  try { document.querySelectorAll('[' + PROCESSED_ATTR + ']').forEach(removeRTL); } catch (e) {}
145
273
  }
@@ -168,10 +296,10 @@ function _agyRtlRendererMain() {
168
296
 
169
297
  function injectCSS() {
170
298
  if (document.getElementById('agy-rtl-styles')) return;
171
- var style = document.createElement('style');
172
- style.id = 'agy-rtl-styles';
173
- style.textContent = CSS;
174
- (document.head || document.documentElement).appendChild(style);
299
+ var s = document.createElement('style');
300
+ s.id = 'agy-rtl-styles';
301
+ s.textContent = CSS;
302
+ (document.head || document.documentElement).appendChild(s);
175
303
  }
176
304
 
177
305
  function createStatusIcon() {
@@ -179,38 +307,55 @@ function _agyRtlRendererMain() {
179
307
 
180
308
  var icon = document.createElement('div');
181
309
  icon.id = STATUS_ICON_ID;
182
- icon.className = 'agy-rtl-status-icon';
310
+ icon.className = 'agy-rtl-status-icon' + (isEnabled ? ' active' : '');
183
311
  icon.setAttribute('title', 'Antigravity Smart RTL');
184
312
 
313
+ // Globe SVG
185
314
  var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
186
- svg.setAttribute('width', '16');
187
- svg.setAttribute('height', '16');
315
+ svg.setAttribute('width', '15');
316
+ svg.setAttribute('height', '15');
188
317
  svg.setAttribute('viewBox', '0 0 24 24');
189
318
  svg.setAttribute('fill', 'none');
190
319
  svg.setAttribute('stroke', 'currentColor');
191
- svg.setAttribute('stroke-width', '2');
320
+ svg.setAttribute('stroke-width', '1.8');
192
321
  svg.setAttribute('stroke-linecap', 'round');
193
322
  svg.setAttribute('stroke-linejoin', 'round');
194
323
 
195
- var p1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
196
- p1.setAttribute('d', 'M16 4H9.5a3.5 3.5 0 1 0 0 7H12');
197
- svg.appendChild(p1);
198
- var l1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
199
- l1.setAttribute('x1', '12'); l1.setAttribute('y1', '4');
200
- l1.setAttribute('x2', '12'); l1.setAttribute('y2', '20');
201
- svg.appendChild(l1);
202
- var l2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
203
- l2.setAttribute('x1', '16'); l2.setAttribute('y1', '4');
204
- l2.setAttribute('x2', '16'); l2.setAttribute('y2', '20');
205
- svg.appendChild(l2);
206
-
324
+ var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
325
+ circle.setAttribute('cx', '12'); circle.setAttribute('cy', '12'); circle.setAttribute('r', '10');
326
+ svg.appendChild(circle);
327
+ var line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
328
+ line1.setAttribute('x1', '2'); line1.setAttribute('y1', '12');
329
+ line1.setAttribute('x2', '22'); line1.setAttribute('y2', '12');
330
+ svg.appendChild(line1);
331
+ var ell = document.createElementNS('http://www.w3.org/2000/svg', 'path');
332
+ ell.setAttribute('d', 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z');
333
+ svg.appendChild(ell);
207
334
  icon.appendChild(svg);
208
335
 
209
- var dot = document.createElement('span');
210
- dot.className = 'agy-rtl-status-dot' + (isEnabled ? ' active' : '');
211
- icon.appendChild(dot);
336
+ var lbl = document.createElement('span');
337
+ lbl.textContent = 'RTL';
338
+ lbl.style.cssText = 'direction:ltr!important;';
339
+ icon.appendChild(lbl);
340
+
341
+ // Hover to open panel
342
+ icon.addEventListener('mouseenter', function () {
343
+ clearTimeout(hoverTimeout);
344
+ hoverTimeout = setTimeout(function () { showPanel(); }, 200);
345
+ });
346
+ icon.addEventListener('mouseleave', function () {
347
+ clearTimeout(hoverTimeout);
348
+ hoverTimeout = setTimeout(function () {
349
+ var panel = document.getElementById(PANEL_ID);
350
+ if (panel && !panel.matches(':hover')) hidePanel();
351
+ }, 300);
352
+ });
353
+ // Also toggle on click
354
+ icon.addEventListener('click', function (e) {
355
+ e.stopPropagation();
356
+ if (panelVisible) hidePanel(); else showPanel();
357
+ });
212
358
 
213
- icon.addEventListener('click', function (e) { e.stopPropagation(); togglePanel(); });
214
359
  document.body.appendChild(icon);
215
360
  }
216
361
 
@@ -221,6 +366,12 @@ function _agyRtlRendererMain() {
221
366
  panel.id = PANEL_ID;
222
367
  panel.className = 'agy-rtl-panel';
223
368
 
369
+ // Keep panel open on hover
370
+ panel.addEventListener('mouseenter', function () { clearTimeout(hoverTimeout); });
371
+ panel.addEventListener('mouseleave', function () {
372
+ hoverTimeout = setTimeout(function () { hidePanel(); }, 300);
373
+ });
374
+
224
375
  // Header
225
376
  var header = document.createElement('div');
226
377
  header.className = 'agy-rtl-panel-header';
@@ -230,7 +381,10 @@ function _agyRtlRendererMain() {
230
381
  header.appendChild(title);
231
382
  panel.appendChild(header);
232
383
 
233
- // Toggle row
384
+ // Body
385
+ var body = document.createElement('div');
386
+ body.className = 'agy-rtl-panel-body';
387
+
234
388
  var row = document.createElement('div');
235
389
  row.className = 'agy-rtl-panel-row';
236
390
  var label = document.createElement('span');
@@ -249,16 +403,12 @@ function _agyRtlRendererMain() {
249
403
  sw.appendChild(cb);
250
404
  sw.appendChild(slider);
251
405
  row.appendChild(sw);
252
- panel.appendChild(row);
253
-
254
- // Separator
255
- var sep = document.createElement('div');
256
- sep.className = 'agy-rtl-panel-sep';
257
- panel.appendChild(sep);
406
+ body.appendChild(row);
407
+ panel.appendChild(body);
258
408
 
259
- // GitHub link
260
- var ghRow = document.createElement('div');
261
- ghRow.className = 'agy-rtl-panel-row agy-rtl-panel-gh';
409
+ // Footer
410
+ var footer = document.createElement('div');
411
+ footer.className = 'agy-rtl-panel-footer';
262
412
  var ghLink = document.createElement('a');
263
413
  ghLink.className = 'agy-rtl-gh-link';
264
414
  ghLink.href = GITHUB_URL;
@@ -266,28 +416,31 @@ function _agyRtlRendererMain() {
266
416
  ghLink.rel = 'noopener noreferrer';
267
417
 
268
418
  var ghSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
269
- ghSvg.setAttribute('width', '14');
270
- ghSvg.setAttribute('height', '14');
271
- ghSvg.setAttribute('viewBox', '0 0 16 16');
272
- ghSvg.setAttribute('fill', 'currentColor');
419
+ ghSvg.setAttribute('width', '12'); ghSvg.setAttribute('height', '12');
420
+ ghSvg.setAttribute('viewBox', '0 0 16 16'); ghSvg.setAttribute('fill', 'currentColor');
273
421
  var ghP = document.createElementNS('http://www.w3.org/2000/svg', 'path');
274
422
  ghP.setAttribute('d', 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z');
275
423
  ghSvg.appendChild(ghP);
276
424
  ghLink.appendChild(ghSvg);
277
-
278
425
  var ghText = document.createElement('span');
279
- ghText.textContent = ' Star on GitHub';
426
+ ghText.textContent = 'Star on GitHub';
280
427
  ghLink.appendChild(ghText);
281
- ghRow.appendChild(ghLink);
282
- panel.appendChild(ghRow);
428
+ footer.appendChild(ghLink);
429
+ panel.appendChild(footer);
283
430
 
284
- // Events
431
+ // Toggle event
285
432
  cb.addEventListener('change', function () {
286
433
  isEnabled = cb.checked;
287
434
  setStoredState(isEnabled);
288
435
  updateState();
289
436
  });
290
437
 
438
+ // Close on Escape
439
+ document.addEventListener('keydown', function (e) {
440
+ if (e.key === 'Escape' && panelVisible) hidePanel();
441
+ });
442
+
443
+ // Close on outside click
291
444
  document.addEventListener('click', function (e) {
292
445
  var p = document.getElementById(PANEL_ID);
293
446
  var ic = document.getElementById(STATUS_ICON_ID);
@@ -297,14 +450,9 @@ function _agyRtlRendererMain() {
297
450
  document.body.appendChild(panel);
298
451
  }
299
452
 
300
- function togglePanel() {
301
- panelVisible ? hidePanel() : showPanel();
302
- }
303
-
304
453
  function showPanel() {
305
454
  var panel = document.getElementById(PANEL_ID);
306
455
  if (!panel) { createPanel(); panel = document.getElementById(PANEL_ID); }
307
-
308
456
  var icon = document.getElementById(STATUS_ICON_ID);
309
457
  if (icon) {
310
458
  var r = icon.getBoundingClientRect();
@@ -322,10 +470,10 @@ function _agyRtlRendererMain() {
322
470
  }
323
471
 
324
472
  function updateState() {
325
- var dot = document.querySelector('.agy-rtl-status-dot');
326
- if (dot) {
327
- if (isEnabled) dot.classList.add('active');
328
- else dot.classList.remove('active');
473
+ var icon = document.getElementById(STATUS_ICON_ID);
474
+ if (icon) {
475
+ if (isEnabled) icon.classList.add('active');
476
+ else icon.classList.remove('active');
329
477
  }
330
478
  var cb = document.getElementById(TOGGLE_BTN_ID);
331
479
  if (cb) cb.checked = isEnabled;