antigravity-rtl-patcher 2.1.0 → 2.2.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/README.md CHANGED
@@ -1,126 +1,80 @@
1
- # 🌌 Antigravity RTL Patcher
1
+ # Antigravity RTL Patcher
2
2
 
3
- <div align="center">
3
+ Smart RTL and Persian/Arabic typography support for the [Antigravity](https://antigravity.dev) desktop app. Patches the Electron runtime to detect right-to-left text and apply proper styling automatically.
4
4
 
5
- ![Version](https://img.shields.io/npm/v/antigravity-rtl-patcher?color=%2300bcd4&style=for-the-badge&label=VERSION)
6
- ![Downloads](https://img.shields.io/npm/dt/antigravity-rtl-patcher?color=%23e040fb&style=for-the-badge)
7
- ![License](https://img.shields.io/badge/LICENSE-MIT-brightgreen?style=for-the-badge)
8
- ![Node](https://img.shields.io/badge/NODE-%3E%3D16-339933?style=for-the-badge&logo=node.js&logoColor=white)
9
-
10
- **Intelligent RTL & Persian Typography Engine for Antigravity**
11
-
12
- *One command. Full right-to-left support. Toggle on/off anytime.*
13
-
14
- </div>
15
-
16
- ---
17
-
18
- ## 🧬 How It Works
19
-
20
- Antigravity is built on Electron. This CLI tool directly patches the app's core workbench files to inject a smart **MutationObserver** engine that:
21
-
22
- 1. 🔍 **Watches** the DOM for new chat messages and AI responses in real-time
23
- 2. 🧠 **Detects** Persian/Arabic/Hebrew text using Unicode analysis with ratio-based scoring
24
- 3. 🎨 **Applies** `direction: rtl` and `unicode-bidi: plaintext` only where needed
25
- 4. ✍️ **Injects** the beautiful **Vazirmatn** font for perfect Persian/Arabic readability
26
- 5. 🔒 **Isolates** English code blocks inside RTL text so they don't break
27
- 6. 🎛️ **Toggle Panel** — Enable/disable RTL support anytime via the in-app panel
5
+ [![npm version](https://img.shields.io/npm/v/antigravity-rtl-patcher)](https://www.npmjs.com/package/antigravity-rtl-patcher)
6
+ [![npm downloads](https://img.shields.io/npm/dt/antigravity-rtl-patcher)](https://www.npmjs.com/package/antigravity-rtl-patcher)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
28
8
 
29
9
  ---
30
10
 
31
- ## ⚡ Quick Start
11
+ ## Install
32
12
 
33
13
  ```bash
34
- # Linux / macOS
35
14
  npx antigravity-rtl-patcher patch
15
+ ```
16
+
17
+ If Antigravity is installed in a system directory, use `sudo`:
36
18
 
37
- # If installed in system directory (e.g., /opt/)
19
+ ```bash
38
20
  sudo npx antigravity-rtl-patcher patch
39
21
  ```
40
22
 
23
+ Restart Antigravity after patching.
24
+
25
+ ## Uninstall
26
+
41
27
  ```bash
42
- # Windows (Run terminal as Administrator if needed)
43
- npx antigravity-rtl-patcher patch
28
+ npx antigravity-rtl-patcher restore
44
29
  ```
45
30
 
46
- That's it. Restart Antigravity and enjoy native RTL support.
47
-
48
- ---
31
+ This restores the original `app.asar` from the backup created during patching.
49
32
 
50
- ## 🎛️ In-App Toggle
33
+ ## Commands
51
34
 
52
- After patching, a small **RTL icon** appears at the bottom of Antigravity's status bar. Click it to open the settings panel:
35
+ | Command | Description |
36
+ |---|---|
37
+ | `agy-rtl patch` | Apply RTL patch |
38
+ | `agy-rtl restore` | Remove patch, restore original |
39
+ | `agy-rtl status` | Show current patch status |
40
+ | `agy-rtl patch --path /custom/path` | Patch a custom installation |
53
41
 
54
- - **Enabled** Toggle RTL support on/off instantly
55
- - Settings are saved automatically and persist across restarts
56
- - Works on every page — enable/disable applies everywhere
42
+ ## How it works
57
43
 
58
- ---
44
+ Antigravity is an Electron app. The patcher extracts `app.asar`, appends an RTL engine to the preload script, and repacks the archive. The engine uses a `MutationObserver` to detect Persian, Arabic, and Hebrew text in real-time and applies `direction: rtl` where needed.
59
45
 
60
- ## 📖 Commands
46
+ After patching, an RTL icon appears at the bottom of the Antigravity window. Click it to open a settings panel where you can enable or disable RTL support. The setting persists across restarts via `localStorage`.
61
47
 
62
- | Command | Description |
63
- |---------|-------------|
64
- | `agy-rtl patch` | Inject RTL support into Antigravity |
65
- | `agy-rtl restore` | Remove patch and restore original files |
66
- | `agy-rtl status` | Check if Antigravity is currently patched |
67
- | `agy-rtl patch --path /custom/path` | Patch a custom installation directory |
48
+ ### What the engine does
68
49
 
69
- ---
50
+ - Detects RTL text using Unicode range analysis with ratio-based scoring
51
+ - Applies directional styling only to elements that contain RTL text
52
+ - Injects the Vazirmatn font for improved Persian/Arabic readability
53
+ - Preserves LTR direction for code blocks inside RTL text
54
+ - Provides a toggle UI to enable/disable at any time
70
55
 
71
- ## 🔄 After Updates
56
+ ## After Antigravity updates
72
57
 
73
- When Antigravity updates, the patch may be overwritten. Simply run the patch command again:
58
+ Updates may overwrite the patch. Run the patch command again:
74
59
 
75
60
  ```bash
76
61
  npx antigravity-rtl-patcher patch
77
62
  ```
78
63
 
79
- ---
80
-
81
- ## 🏗️ Architecture
82
-
83
- ```
84
- antigravity-rtl-patcher/
85
- ├── bin/
86
- │ └── cli.js # CLI entry point (commander-based)
87
- ├── src/
88
- │ ├── patcher.js # Core logic: backup, inject, repack
89
- │ ├── paths.js # OS-aware installation path detection
90
- │ └── utils.js # Permission checks & helpers
91
- ├── payload/
92
- │ ├── rtl-engine.js # MutationObserver RTL engine + Toggle UI
93
- │ └── styles.css # Vazirmatn font + RTL + Panel styles
94
- ├── package.json
95
- └── README.md
96
- ```
97
-
98
- ### Supported Installation Types
99
-
100
- | Type | Description | Method |
101
- |------|-------------|--------|
102
- | **Unpacked** | App with `resources/app/` directory | Direct file modification |
103
- | **ASAR** | App bundled as `app.asar` | Extract → Patch → Repack |
104
-
105
- ---
106
-
107
- ## 🔐 Permissions
64
+ ## Supported platforms
108
65
 
109
- - **User directory installs** (e.g., `~/Downloads/`): No special permissions needed
110
- - **System directory installs** (e.g., `/opt/`): Requires `sudo` on Linux/macOS
111
-
112
- ---
66
+ | Platform | Typical install locations |
67
+ |---|---|
68
+ | Linux | `/opt/antigravity`, `~/Downloads/Antigravity-x64` |
69
+ | macOS | `/Applications/Antigravity.app` |
70
+ | Windows | `%LOCALAPPDATA%\Programs\Antigravity` |
113
71
 
114
- ## 🤝 Contributing
72
+ Custom paths are supported via `--path`.
115
73
 
116
- Contributions, issues, and feature requests are welcome!
74
+ ## Contributing
117
75
 
118
- 1. Fork the repo
119
- 2. Create your feature branch (`git checkout -b feature/amazing`)
120
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
121
- 4. Push to the branch (`git push origin feature/amazing`)
122
- 5. Open a Pull Request
76
+ Issues and pull requests are welcome at [github.com/VaFa1726/antigravity-rtl-patcher](https://github.com/VaFa1726/antigravity-rtl-patcher).
123
77
 
124
- ## 📄 License
78
+ ## License
125
79
 
126
- [MIT](https://choosealicense.com/licenses/mit/)
80
+ MIT
package/bin/cli.js CHANGED
@@ -5,16 +5,13 @@ const chalk = require('chalk');
5
5
  const { patch, restore, status } = require('../src/patcher');
6
6
 
7
7
  const BANNER = `
8
- ${chalk.cyan('╔══════════════════════════════════════════════╗')}
9
- ${chalk.cyan('║')} ${chalk.bold.white('🌌 Antigravity RTL Patcher')} ${chalk.gray('v2.1.0')} ${chalk.cyan('║')}
10
- ${chalk.cyan('║')} ${chalk.gray('Intelligent RTL support for Antigravity')} ${chalk.cyan('║')}
11
- ${chalk.cyan('╚══════════════════════════════════════════════╝')}
8
+ ${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.2.0')}
12
9
  `;
13
10
 
14
11
  program
15
12
  .name('agy-rtl')
16
- .description('RTL & Typography patcher for Antigravity')
17
- .version('2.1.0');
13
+ .description('RTL patcher for Antigravity')
14
+ .version('2.2.0');
18
15
 
19
16
  program
20
17
  .command('patch')
@@ -25,35 +22,35 @@ program
25
22
  try {
26
23
  await patch(options.path);
27
24
  } catch (err) {
28
- console.error(chalk.red('\n❌ Patch failed:'), err.message);
25
+ console.error(chalk.red('\nPatch failed:'), err.message);
29
26
  process.exit(1);
30
27
  }
31
28
  });
32
29
 
33
30
  program
34
31
  .command('restore')
35
- .description('Restore Antigravity to its original state')
32
+ .description('Remove RTL patch and restore Antigravity')
36
33
  .option('-p, --path <path>', 'Custom path to Antigravity installation')
37
34
  .action(async (options) => {
38
35
  console.log(BANNER);
39
36
  try {
40
37
  await restore(options.path);
41
38
  } catch (err) {
42
- console.error(chalk.red('\n❌ Restore failed:'), err.message);
39
+ console.error(chalk.red('\nRestore failed:'), err.message);
43
40
  process.exit(1);
44
41
  }
45
42
  });
46
43
 
47
44
  program
48
45
  .command('status')
49
- .description('Check if Antigravity is currently patched')
46
+ .description('Check current patch status')
50
47
  .option('-p, --path <path>', 'Custom path to Antigravity installation')
51
48
  .action(async (options) => {
52
49
  console.log(BANNER);
53
50
  try {
54
51
  await status(options.path);
55
52
  } catch (err) {
56
- console.error(chalk.red('\n❌ Status check failed:'), err.message);
53
+ console.error(chalk.red('\nStatus check failed:'), err.message);
57
54
  process.exit(1);
58
55
  }
59
56
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "antigravity-rtl-patcher",
3
- "version": "2.1.0",
4
- "description": "🌌 Intelligent RTL & Persian/Arabic typography patcher for Antigravity",
3
+ "version": "2.2.0",
4
+ "description": "Smart RTL and Persian/Arabic typography patcher for Antigravity",
5
5
  "main": "src/patcher.js",
6
6
  "bin": {
7
7
  "antigravity-rtl-patcher": "./bin/cli.js",
@@ -0,0 +1,351 @@
1
+ /**
2
+ * AGY RTL — Preload Injection Snippet
3
+ * Appended to Antigravity's preload.js at patch time.
4
+ * Injects the RTL engine + toggle panel into every renderer page.
5
+ *
6
+ * MARKER: __AGY_RTL_INJECTED__
7
+ */
8
+ (function _agyRtlPreloadInit() {
9
+ // Only run in renderer context
10
+ if (typeof document === 'undefined') {
11
+ // We're in the preload context before DOM exists.
12
+ // Schedule injection for when the page actually loads.
13
+ const { webFrame } = require('electron');
14
+
15
+ webFrame.executeJavaScript(`(${_agyRtlRendererMain.toString()})();`);
16
+ return;
17
+ }
18
+ _agyRtlRendererMain();
19
+ })();
20
+
21
+ function _agyRtlRendererMain() {
22
+ 'use strict';
23
+
24
+ // Prevent double injection
25
+ if (window.__AGY_RTL_LOADED__) return;
26
+ window.__AGY_RTL_LOADED__ = true;
27
+
28
+ // ─── Configuration ───────────────────────────────────────
29
+ var STORAGE_KEY = 'agy-rtl-enabled';
30
+ var PANEL_ID = 'agy-rtl-panel';
31
+ var TOGGLE_BTN_ID = 'agy-rtl-toggle-btn';
32
+ var STATUS_ICON_ID = 'agy-rtl-status-icon';
33
+ var GITHUB_URL = 'https://github.com/VaFa1726/antigravity-rtl-patcher';
34
+
35
+ // RTL detection
36
+ var RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
37
+ 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(', ');
56
+
57
+ // ─── State ───────────────────────────────────────────────
58
+ var isEnabled = getStoredState();
59
+ var observer = null;
60
+ var panelVisible = false;
61
+
62
+ // ─── 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');
94
+
95
+ // ─── Helpers ─────────────────────────────────────────────
96
+
97
+ function getStoredState() {
98
+ try {
99
+ var s = localStorage.getItem(STORAGE_KEY);
100
+ return s === null ? true : s === 'true';
101
+ } catch (e) { return true; }
102
+ }
103
+
104
+ function setStoredState(v) {
105
+ try { localStorage.setItem(STORAGE_KEY, v ? 'true' : 'false'); }
106
+ catch (e) { /* ignore */ }
107
+ }
108
+
109
+ function isRTL(text) {
110
+ if (!text || text.trim().length === 0) return false;
111
+ if (!RTL_REGEX.test(text)) return false;
112
+ var m = text.match(RTL_CHAR_REGEX);
113
+ if (!m) return false;
114
+ var alpha = text.replace(/[\s\d\W]/g, '').length;
115
+ return alpha > 0 && (m.length / alpha) >= MIN_RTL_RATIO;
116
+ }
117
+
118
+ function applyRTL(el) {
119
+ if (el.getAttribute(PROCESSED_ATTR)) return;
120
+ if (isRTL(el.textContent || '')) {
121
+ el.setAttribute(PROCESSED_ATTR, 'true');
122
+ el.style.direction = 'rtl';
123
+ el.style.textAlign = 'start';
124
+ el.style.unicodeBidi = 'plaintext';
125
+ }
126
+ }
127
+
128
+ function removeRTL(el) {
129
+ if (el.getAttribute(PROCESSED_ATTR)) {
130
+ el.removeAttribute(PROCESSED_ATTR);
131
+ el.style.direction = '';
132
+ el.style.textAlign = '';
133
+ el.style.unicodeBidi = '';
134
+ }
135
+ }
136
+
137
+ function scanElement(root) {
138
+ 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
+ }
142
+
143
+ function clearAllRTL() {
144
+ try { document.querySelectorAll('[' + PROCESSED_ATTR + ']').forEach(removeRTL); } catch (e) {}
145
+ }
146
+
147
+ // ─── Observer ────────────────────────────────────────────
148
+
149
+ function startObserver() {
150
+ if (observer) return;
151
+ observer = new MutationObserver(function (muts) {
152
+ if (!isEnabled) return;
153
+ muts.forEach(function (m) {
154
+ m.addedNodes.forEach(function (n) { if (n.nodeType === 1) scanElement(n); });
155
+ if (m.type === 'characterData' && m.target.parentElement) applyRTL(m.target.parentElement);
156
+ });
157
+ });
158
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
159
+ scanElement(document.body);
160
+ }
161
+
162
+ function stopObserver() {
163
+ if (observer) { observer.disconnect(); observer = null; }
164
+ clearAllRTL();
165
+ }
166
+
167
+ // ─── UI ──────────────────────────────────────────────────
168
+
169
+ function injectCSS() {
170
+ 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);
175
+ }
176
+
177
+ function createStatusIcon() {
178
+ if (document.getElementById(STATUS_ICON_ID)) return;
179
+
180
+ var icon = document.createElement('div');
181
+ icon.id = STATUS_ICON_ID;
182
+ icon.className = 'agy-rtl-status-icon';
183
+ icon.setAttribute('title', 'Antigravity Smart RTL');
184
+
185
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
186
+ svg.setAttribute('width', '16');
187
+ svg.setAttribute('height', '16');
188
+ svg.setAttribute('viewBox', '0 0 24 24');
189
+ svg.setAttribute('fill', 'none');
190
+ svg.setAttribute('stroke', 'currentColor');
191
+ svg.setAttribute('stroke-width', '2');
192
+ svg.setAttribute('stroke-linecap', 'round');
193
+ svg.setAttribute('stroke-linejoin', 'round');
194
+
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
+
207
+ icon.appendChild(svg);
208
+
209
+ var dot = document.createElement('span');
210
+ dot.className = 'agy-rtl-status-dot' + (isEnabled ? ' active' : '');
211
+ icon.appendChild(dot);
212
+
213
+ icon.addEventListener('click', function (e) { e.stopPropagation(); togglePanel(); });
214
+ document.body.appendChild(icon);
215
+ }
216
+
217
+ function createPanel() {
218
+ if (document.getElementById(PANEL_ID)) return;
219
+
220
+ var panel = document.createElement('div');
221
+ panel.id = PANEL_ID;
222
+ panel.className = 'agy-rtl-panel';
223
+
224
+ // Header
225
+ var header = document.createElement('div');
226
+ header.className = 'agy-rtl-panel-header';
227
+ var title = document.createElement('span');
228
+ title.className = 'agy-rtl-panel-title';
229
+ title.textContent = 'Antigravity Smart RTL';
230
+ header.appendChild(title);
231
+ panel.appendChild(header);
232
+
233
+ // Toggle row
234
+ var row = document.createElement('div');
235
+ row.className = 'agy-rtl-panel-row';
236
+ var label = document.createElement('span');
237
+ label.className = 'agy-rtl-panel-label';
238
+ label.textContent = 'Enabled';
239
+ row.appendChild(label);
240
+
241
+ var sw = document.createElement('label');
242
+ sw.className = 'agy-rtl-switch';
243
+ var cb = document.createElement('input');
244
+ cb.type = 'checkbox';
245
+ cb.id = TOGGLE_BTN_ID;
246
+ cb.checked = isEnabled;
247
+ var slider = document.createElement('span');
248
+ slider.className = 'agy-rtl-slider';
249
+ sw.appendChild(cb);
250
+ sw.appendChild(slider);
251
+ 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);
258
+
259
+ // GitHub link
260
+ var ghRow = document.createElement('div');
261
+ ghRow.className = 'agy-rtl-panel-row agy-rtl-panel-gh';
262
+ var ghLink = document.createElement('a');
263
+ ghLink.className = 'agy-rtl-gh-link';
264
+ ghLink.href = GITHUB_URL;
265
+ ghLink.target = '_blank';
266
+ ghLink.rel = 'noopener noreferrer';
267
+
268
+ 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');
273
+ var ghP = document.createElementNS('http://www.w3.org/2000/svg', 'path');
274
+ 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
+ ghSvg.appendChild(ghP);
276
+ ghLink.appendChild(ghSvg);
277
+
278
+ var ghText = document.createElement('span');
279
+ ghText.textContent = ' Star on GitHub';
280
+ ghLink.appendChild(ghText);
281
+ ghRow.appendChild(ghLink);
282
+ panel.appendChild(ghRow);
283
+
284
+ // Events
285
+ cb.addEventListener('change', function () {
286
+ isEnabled = cb.checked;
287
+ setStoredState(isEnabled);
288
+ updateState();
289
+ });
290
+
291
+ document.addEventListener('click', function (e) {
292
+ var p = document.getElementById(PANEL_ID);
293
+ var ic = document.getElementById(STATUS_ICON_ID);
294
+ if (p && panelVisible && !p.contains(e.target) && !ic.contains(e.target)) hidePanel();
295
+ });
296
+
297
+ document.body.appendChild(panel);
298
+ }
299
+
300
+ function togglePanel() {
301
+ panelVisible ? hidePanel() : showPanel();
302
+ }
303
+
304
+ function showPanel() {
305
+ var panel = document.getElementById(PANEL_ID);
306
+ if (!panel) { createPanel(); panel = document.getElementById(PANEL_ID); }
307
+
308
+ var icon = document.getElementById(STATUS_ICON_ID);
309
+ if (icon) {
310
+ var r = icon.getBoundingClientRect();
311
+ panel.style.bottom = (window.innerHeight - r.top + 8) + 'px';
312
+ panel.style.right = (window.innerWidth - r.right) + 'px';
313
+ }
314
+ panel.classList.add('visible');
315
+ panelVisible = true;
316
+ }
317
+
318
+ function hidePanel() {
319
+ var panel = document.getElementById(PANEL_ID);
320
+ if (panel) panel.classList.remove('visible');
321
+ panelVisible = false;
322
+ }
323
+
324
+ 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');
329
+ }
330
+ var cb = document.getElementById(TOGGLE_BTN_ID);
331
+ if (cb) cb.checked = isEnabled;
332
+
333
+ if (isEnabled) startObserver();
334
+ else stopObserver();
335
+ }
336
+
337
+ // ─── Init ────────────────────────────────────────────────
338
+
339
+ function init() {
340
+ injectCSS();
341
+ createStatusIcon();
342
+ createPanel();
343
+ updateState();
344
+ }
345
+
346
+ if (document.readyState === 'loading') {
347
+ document.addEventListener('DOMContentLoaded', init);
348
+ } else {
349
+ setTimeout(init, 800);
350
+ }
351
+ }