reactoradar 1.6.4 → 1.6.6
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/app.js +74 -4236
- package/index.html +12 -0
- package/main.js +86 -9
- package/package.json +1 -1
- package/preload.js +2 -1
- package/styles.css +130 -7
- package/src/main/main.js +0 -396
- package/src/main/preload.js +0 -28
- package/src/renderer/app.js +0 -221
- package/src/renderer/components/object-tree.js +0 -245
- package/src/renderer/index.html +0 -111
- package/src/renderer/panels/console.js +0 -248
- package/src/renderer/panels/memory.js +0 -60
- package/src/renderer/panels/network.js +0 -559
- package/src/renderer/panels/performance.js +0 -144
- package/src/renderer/panels/react.js +0 -31
- package/src/renderer/panels/redux.js +0 -159
- package/src/renderer/panels/settings.js +0 -93
- package/src/renderer/panels/sources.js +0 -189
- package/src/renderer/panels/storage.js +0 -134
- package/src/renderer/state.js +0 -132
- package/src/renderer/styles/components.css +0 -145
- package/src/renderer/styles/console.css +0 -73
- package/src/renderer/styles/main.css +0 -229
- package/src/renderer/styles/network.css +0 -242
- package/src/renderer/styles/performance.css +0 -45
- package/src/renderer/styles/redux.css +0 -77
- package/src/renderer/styles/settings.css +0 -63
- package/src/renderer/styles/sources.css +0 -48
- package/src/renderer/styles/storage.css +0 -28
- package/src/renderer/styles/theme-light.css +0 -57
package/index.html
CHANGED
|
@@ -90,6 +90,18 @@
|
|
|
90
90
|
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
|
+
<!-- Load order: app.js (state/helpers) → panels → init.js (IPC/boot) -->
|
|
93
94
|
<script src="app.js"></script>
|
|
95
|
+
<script src="panels/settings.js"></script>
|
|
96
|
+
<script src="panels/console.js"></script>
|
|
97
|
+
<script src="panels/network.js"></script>
|
|
98
|
+
<script src="panels/ga4.js"></script>
|
|
99
|
+
<script src="panels/redux.js"></script>
|
|
100
|
+
<script src="panels/storage.js"></script>
|
|
101
|
+
<script src="panels/performance.js"></script>
|
|
102
|
+
<script src="panels/native.js"></script>
|
|
103
|
+
<script src="panels/react.js"></script>
|
|
104
|
+
<script src="panels/sources.js"></script>
|
|
105
|
+
<script src="init.js"></script>
|
|
94
106
|
</body>
|
|
95
107
|
</html>
|
package/main.js
CHANGED
|
@@ -35,6 +35,7 @@ function _send(channel, ...args) {
|
|
|
35
35
|
let reduxClients = new Set();
|
|
36
36
|
let storageClients = new Set();
|
|
37
37
|
let networkClients = new Set();
|
|
38
|
+
const _bridgeServers = []; // track bridge WSS instances for cleanup on quit
|
|
38
39
|
|
|
39
40
|
// ─── Set dock icon ASAP (before app ready) ──────────────────────────────────
|
|
40
41
|
const _appIcon = nativeImage.createFromPath(path.join(__dirname, 'ReactoRadar.png'));
|
|
@@ -74,15 +75,16 @@ if (gotLock) app.whenReady().then(async () => {
|
|
|
74
75
|
|
|
75
76
|
await createMainWindow();
|
|
76
77
|
|
|
77
|
-
// Send version to renderer — try package.json, fallback to app.getVersion()
|
|
78
|
+
// Send version + install type to renderer — try package.json, fallback to app.getVersion()
|
|
78
79
|
let appVersion;
|
|
79
80
|
try { appVersion = require('./package.json').version; } catch { appVersion = app.getVersion(); }
|
|
81
|
+
const isPackaged = app.isPackaged;
|
|
80
82
|
// Send multiple times to ensure renderer catches it (covers race conditions)
|
|
81
83
|
mainWindow.webContents.on('did-finish-load', () => {
|
|
82
84
|
// Send immediately + retries
|
|
83
|
-
_send('app-version', appVersion);
|
|
85
|
+
_send('app-version', appVersion, isPackaged);
|
|
84
86
|
[500, 2000, 5000].forEach(delay => {
|
|
85
|
-
setTimeout(() => _send('app-version', appVersion), delay);
|
|
87
|
+
setTimeout(() => _send('app-version', appVersion, isPackaged), delay);
|
|
86
88
|
});
|
|
87
89
|
});
|
|
88
90
|
|
|
@@ -102,12 +104,27 @@ app.on('window-all-closed', () => {
|
|
|
102
104
|
|
|
103
105
|
app.on('before-quit', () => {
|
|
104
106
|
_forceQuit = true;
|
|
107
|
+
// Free renderer memory before shutdown (logs are not cleared — user may still see them briefly)
|
|
108
|
+
_send('device-all-disconnected');
|
|
109
|
+
// Close CDP DevTools window if open
|
|
110
|
+
if (devtoolsWindow && !devtoolsWindow.isDestroyed()) {
|
|
111
|
+
devtoolsWindow.destroy();
|
|
112
|
+
devtoolsWindow = null;
|
|
113
|
+
}
|
|
105
114
|
// Close all WS servers gracefully
|
|
106
115
|
if (reactDTServer) {
|
|
107
116
|
reactDTServer.close();
|
|
108
117
|
reactDTClients.forEach(ws => ws.close());
|
|
109
118
|
reactDTClients.clear();
|
|
110
119
|
}
|
|
120
|
+
// Close bridge servers and disconnect all clients
|
|
121
|
+
_bridgeServers.forEach(wss => {
|
|
122
|
+
wss.clients.forEach(ws => ws.close());
|
|
123
|
+
wss.close();
|
|
124
|
+
});
|
|
125
|
+
reduxClients.clear();
|
|
126
|
+
storageClients.clear();
|
|
127
|
+
networkClients.clear();
|
|
111
128
|
});
|
|
112
129
|
|
|
113
130
|
app.on('activate', () => {
|
|
@@ -336,6 +353,10 @@ function startReactDevToolsServer() {
|
|
|
336
353
|
});
|
|
337
354
|
});
|
|
338
355
|
|
|
356
|
+
ws.on('error', (err) => {
|
|
357
|
+
console.warn(`[ReactDT] Client error:`, err.message);
|
|
358
|
+
});
|
|
359
|
+
|
|
339
360
|
ws.on('close', () => {
|
|
340
361
|
reactDTClients.delete(ws);
|
|
341
362
|
console.log(`[ReactDT] Client disconnected (total: ${reactDTClients.size})`);
|
|
@@ -354,6 +375,7 @@ function startReactDevToolsServer() {
|
|
|
354
375
|
function startBridgeServers() {
|
|
355
376
|
// Redux Bridge
|
|
356
377
|
startBridge(PORTS.REDUX_BRIDGE, 'redux', reduxClients, (event) => {
|
|
378
|
+
// console.log('[REDUX-DEBUG] Event from SDK:', event?.type, event?.action?.type);
|
|
357
379
|
_send('redux-event', event);
|
|
358
380
|
});
|
|
359
381
|
|
|
@@ -380,6 +402,7 @@ function startBridgeServers() {
|
|
|
380
402
|
function startBridge(port, name, clients, onEvent) {
|
|
381
403
|
try {
|
|
382
404
|
const wss = new WebSocketServer({ port });
|
|
405
|
+
_bridgeServers.push(wss);
|
|
383
406
|
wss.on('error', (err) => {
|
|
384
407
|
if (err.code === 'EADDRINUSE') {
|
|
385
408
|
console.error(`[${name}] Port ${port} is already in use — another ReactoRadar or debugger may be running.`);
|
|
@@ -404,10 +427,19 @@ function startBridge(port, name, clients, onEvent) {
|
|
|
404
427
|
}
|
|
405
428
|
});
|
|
406
429
|
|
|
430
|
+
ws.on('error', (err) => {
|
|
431
|
+
console.warn(`[${name}] Client error:`, err.message);
|
|
432
|
+
});
|
|
433
|
+
|
|
407
434
|
ws.on('close', () => {
|
|
408
435
|
clients.delete(ws);
|
|
409
436
|
if (clients.size === 0) {
|
|
410
437
|
_send(`${name}-connected`, false);
|
|
438
|
+
// When every bridge has zero clients, tell the renderer to clear old data
|
|
439
|
+
if (reduxClients.size === 0 && storageClients.size === 0 && networkClients.size === 0) {
|
|
440
|
+
console.log('[Bridge] All device connections closed — sending clear signal');
|
|
441
|
+
_send('device-all-disconnected');
|
|
442
|
+
}
|
|
411
443
|
}
|
|
412
444
|
});
|
|
413
445
|
});
|
|
@@ -589,6 +621,9 @@ function setupIPC() {
|
|
|
589
621
|
});
|
|
590
622
|
|
|
591
623
|
ipcMain.handle('fetch-changelog', async (_, version) => {
|
|
624
|
+
if (!version || typeof version !== 'string' || !/^[\d]+\.[\d]+\.[\d]+/.test(version)) {
|
|
625
|
+
return 'Invalid version.';
|
|
626
|
+
}
|
|
592
627
|
return new Promise((resolve) => {
|
|
593
628
|
https.get(`https://api.github.com/repos/sharanagouda/reactoradar/releases/tags/v${version}`, {
|
|
594
629
|
headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
|
|
@@ -603,6 +638,44 @@ function setupIPC() {
|
|
|
603
638
|
});
|
|
604
639
|
});
|
|
605
640
|
|
|
641
|
+
// Fetch all releases for version history / rollback
|
|
642
|
+
ipcMain.handle('fetch-releases', async () => {
|
|
643
|
+
return new Promise((resolve) => {
|
|
644
|
+
https.get('https://api.github.com/repos/sharanagouda/reactoradar/releases?per_page=20', {
|
|
645
|
+
headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
|
|
646
|
+
}, (res) => {
|
|
647
|
+
let data = '';
|
|
648
|
+
res.on('data', d => data += d);
|
|
649
|
+
res.on('end', () => {
|
|
650
|
+
try {
|
|
651
|
+
const releases = JSON.parse(data);
|
|
652
|
+
if (!Array.isArray(releases)) { resolve([]); return; }
|
|
653
|
+
const mapped = [];
|
|
654
|
+
for (const r of releases) {
|
|
655
|
+
if (!r || typeof r !== 'object') continue;
|
|
656
|
+
const tag = r.tag_name || '';
|
|
657
|
+
const version = tag.replace(/^v/, '');
|
|
658
|
+
if (!version) continue; // skip entries with no version
|
|
659
|
+
const assets = Array.isArray(r.assets) ? r.assets : [];
|
|
660
|
+
mapped.push({
|
|
661
|
+
version,
|
|
662
|
+
tag,
|
|
663
|
+
name: r.name || tag || version,
|
|
664
|
+
date: r.published_at || null,
|
|
665
|
+
prerelease: !!r.prerelease,
|
|
666
|
+
body: r.body || '',
|
|
667
|
+
dmgUrl: (assets.find(a => a && a.name && a.name.endsWith('.dmg')) || {}).browser_download_url || '',
|
|
668
|
+
zipUrl: (assets.find(a => a && a.name && a.name.endsWith('.zip')) || {}).browser_download_url || '',
|
|
669
|
+
htmlUrl: r.html_url || '',
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
resolve(mapped);
|
|
673
|
+
} catch { resolve([]); }
|
|
674
|
+
});
|
|
675
|
+
}).on('error', () => resolve([]));
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
|
|
606
679
|
ipcMain.on('install-update', () => {
|
|
607
680
|
if (autoUpdater) {
|
|
608
681
|
autoUpdater.quitAndInstall(false, true);
|
|
@@ -654,10 +727,9 @@ function setupIPC() {
|
|
|
654
727
|
});
|
|
655
728
|
|
|
656
729
|
ipcMain.on('start-native-logs', (_, platform) => {
|
|
657
|
-
// Kill existing process
|
|
730
|
+
// Kill existing process
|
|
658
731
|
if (_nativeLogProcess) {
|
|
659
|
-
try {
|
|
660
|
-
try { _nativeLogProcess.kill(); } catch {}
|
|
732
|
+
try { _nativeLogProcess.kill('SIGTERM'); } catch {}
|
|
661
733
|
_nativeLogProcess = null;
|
|
662
734
|
}
|
|
663
735
|
|
|
@@ -665,9 +737,9 @@ function setupIPC() {
|
|
|
665
737
|
let cmd, args;
|
|
666
738
|
|
|
667
739
|
if (platform === 'android') {
|
|
668
|
-
// adb logcat —
|
|
740
|
+
// adb logcat — show only new logs from now (not historical buffer)
|
|
669
741
|
cmd = 'adb';
|
|
670
|
-
args = ['logcat', '-v', 'threadtime', '*:W']; //
|
|
742
|
+
args = ['logcat', '-v', 'threadtime', '-T', '1', '*:W']; // -T 1 = last 1 line then real-time
|
|
671
743
|
} else if (platform === 'ios-sim') {
|
|
672
744
|
// xcrun simctl for iOS Simulator — use syslog style for parseable output
|
|
673
745
|
cmd = 'xcrun';
|
|
@@ -682,9 +754,10 @@ function setupIPC() {
|
|
|
682
754
|
}
|
|
683
755
|
|
|
684
756
|
try {
|
|
685
|
-
_nativeLogProcess = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe']
|
|
757
|
+
_nativeLogProcess = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
686
758
|
|
|
687
759
|
_send('native-status', { connected: true, platform });
|
|
760
|
+
console.log(`[NativeLogs] Started ${cmd} ${args.join(' ')} (pid: ${_nativeLogProcess.pid})`);
|
|
688
761
|
|
|
689
762
|
let buffer = '';
|
|
690
763
|
_nativeLogProcess.stdout.on('data', (chunk) => {
|
|
@@ -703,6 +776,10 @@ function setupIPC() {
|
|
|
703
776
|
if (text) _send('native-log', { level: 'error', message: text, source: 'stderr', ts: Date.now() });
|
|
704
777
|
});
|
|
705
778
|
|
|
779
|
+
// Guard against stream errors (broken pipe, etc.)
|
|
780
|
+
_nativeLogProcess.stdout.on('error', () => {});
|
|
781
|
+
_nativeLogProcess.stderr.on('error', () => {});
|
|
782
|
+
|
|
706
783
|
_nativeLogProcess.on('close', (code) => {
|
|
707
784
|
_nativeLogProcess = null;
|
|
708
785
|
_send('native-status', { connected: false, error: code ? `Process exited with code ${code}` : 'Disconnected' });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.6",
|
|
5
5
|
"description": "macOS debugger for React Native — Console, Sources, Network, Performance, Memory, Redux, AsyncStorage, React tree. Supports RN 0.74+ with Hermes and New Architecture.",
|
|
6
6
|
"main": "main.js",
|
|
7
7
|
"bin": {
|
package/preload.js
CHANGED
|
@@ -11,7 +11,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
11
11
|
'ports', 'cdp-targets', 'redux-event', 'storage-event', 'network-event',
|
|
12
12
|
'console-event', 'perf-event', 'ga4-event', 'redux-connected', 'storage-connected', 'network-connected',
|
|
13
13
|
'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed', 'update-available', 'update-downloaded', 'app-version', 'focus-search',
|
|
14
|
-
'native-log', 'native-status',
|
|
14
|
+
'native-log', 'native-status', 'device-all-disconnected',
|
|
15
15
|
];
|
|
16
16
|
if (allowed.includes(channel)) {
|
|
17
17
|
ipcRenderer.removeAllListeners(channel);
|
|
@@ -36,4 +36,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
36
36
|
stopNativeLogs: () => ipcRenderer.send('stop-native-logs'),
|
|
37
37
|
detectNativePlatform: () => ipcRenderer.invoke('detect-native-platform'),
|
|
38
38
|
fetchChangelog: (version) => ipcRenderer.invoke('fetch-changelog', version),
|
|
39
|
+
fetchReleases: () => ipcRenderer.invoke('fetch-releases'),
|
|
39
40
|
});
|
package/styles.css
CHANGED
|
@@ -1320,6 +1320,123 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1320
1320
|
text-decoration: underline;
|
|
1321
1321
|
}
|
|
1322
1322
|
|
|
1323
|
+
/* Version History */
|
|
1324
|
+
.version-history-list {
|
|
1325
|
+
max-height: 300px;
|
|
1326
|
+
overflow-y: auto;
|
|
1327
|
+
border: 1px solid var(--border);
|
|
1328
|
+
border-radius: 6px;
|
|
1329
|
+
background: var(--bg2);
|
|
1330
|
+
}
|
|
1331
|
+
.version-history-list::-webkit-scrollbar { width: 3px; }
|
|
1332
|
+
.version-history-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
|
|
1333
|
+
.version-row {
|
|
1334
|
+
display: flex;
|
|
1335
|
+
align-items: center;
|
|
1336
|
+
justify-content: space-between;
|
|
1337
|
+
padding: 8px 12px;
|
|
1338
|
+
border-bottom: 1px solid var(--border);
|
|
1339
|
+
font-size: 11px;
|
|
1340
|
+
transition: background 0.15s;
|
|
1341
|
+
}
|
|
1342
|
+
.version-row:last-child { border-bottom: none; }
|
|
1343
|
+
.version-row:hover { background: var(--bg3); }
|
|
1344
|
+
.version-row.version-current { background: color-mix(in srgb, var(--accent) 8%, transparent); }
|
|
1345
|
+
.version-info { display: flex; flex-direction: column; gap: 2px; }
|
|
1346
|
+
.version-tag { color: var(--text); font-weight: 600; font-size: 12px; }
|
|
1347
|
+
.version-date { color: var(--text-dim); font-size: 10px; }
|
|
1348
|
+
.version-badge {
|
|
1349
|
+
display: inline-block;
|
|
1350
|
+
background: var(--green);
|
|
1351
|
+
color: #000;
|
|
1352
|
+
font-size: 9px;
|
|
1353
|
+
font-weight: 700;
|
|
1354
|
+
padding: 1px 5px;
|
|
1355
|
+
border-radius: 3px;
|
|
1356
|
+
margin-left: 6px;
|
|
1357
|
+
vertical-align: middle;
|
|
1358
|
+
text-transform: uppercase;
|
|
1359
|
+
}
|
|
1360
|
+
.version-pre {
|
|
1361
|
+
display: inline-block;
|
|
1362
|
+
background: var(--yellow);
|
|
1363
|
+
color: #000;
|
|
1364
|
+
font-size: 9px;
|
|
1365
|
+
font-weight: 700;
|
|
1366
|
+
padding: 1px 5px;
|
|
1367
|
+
border-radius: 3px;
|
|
1368
|
+
margin-left: 4px;
|
|
1369
|
+
vertical-align: middle;
|
|
1370
|
+
}
|
|
1371
|
+
.version-actions { display: flex; gap: 6px; align-items: center; }
|
|
1372
|
+
.version-installed {
|
|
1373
|
+
font-size: 10px;
|
|
1374
|
+
color: var(--green);
|
|
1375
|
+
font-weight: 600;
|
|
1376
|
+
}
|
|
1377
|
+
.version-install-btn {
|
|
1378
|
+
background: var(--accent);
|
|
1379
|
+
color: #fff;
|
|
1380
|
+
border: none;
|
|
1381
|
+
border-radius: 4px;
|
|
1382
|
+
padding: 3px 10px;
|
|
1383
|
+
font-size: 10px;
|
|
1384
|
+
font-weight: 600;
|
|
1385
|
+
cursor: pointer;
|
|
1386
|
+
transition: opacity 0.15s;
|
|
1387
|
+
}
|
|
1388
|
+
.version-install-btn:hover { opacity: 0.85; }
|
|
1389
|
+
.version-notes-btn {
|
|
1390
|
+
background: transparent;
|
|
1391
|
+
color: var(--text-mid);
|
|
1392
|
+
border: 1px solid var(--border);
|
|
1393
|
+
border-radius: 4px;
|
|
1394
|
+
padding: 3px 8px;
|
|
1395
|
+
font-size: 10px;
|
|
1396
|
+
cursor: pointer;
|
|
1397
|
+
transition: color 0.15s, border-color 0.15s;
|
|
1398
|
+
}
|
|
1399
|
+
.version-dl-wrap { position: relative; }
|
|
1400
|
+
.version-dl-menu {
|
|
1401
|
+
position: absolute;
|
|
1402
|
+
top: 100%;
|
|
1403
|
+
right: 0;
|
|
1404
|
+
z-index: 100;
|
|
1405
|
+
min-width: 220px;
|
|
1406
|
+
background: var(--bg2);
|
|
1407
|
+
border: 1px solid var(--border);
|
|
1408
|
+
border-radius: 8px;
|
|
1409
|
+
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
|
1410
|
+
padding: 4px 0;
|
|
1411
|
+
margin-top: 4px;
|
|
1412
|
+
animation: changelogSlideIn 0.12s ease-out;
|
|
1413
|
+
}
|
|
1414
|
+
.version-dl-item {
|
|
1415
|
+
display: flex;
|
|
1416
|
+
align-items: center;
|
|
1417
|
+
gap: 10px;
|
|
1418
|
+
padding: 8px 14px;
|
|
1419
|
+
cursor: pointer;
|
|
1420
|
+
transition: background 0.12s;
|
|
1421
|
+
}
|
|
1422
|
+
.version-dl-item:hover { background: var(--bg3); }
|
|
1423
|
+
.version-dl-icon { font-size: 16px; flex-shrink: 0; }
|
|
1424
|
+
.version-dl-name { font-size: 11px; font-weight: 600; color: var(--text); }
|
|
1425
|
+
.version-dl-hint { font-size: 9px; color: var(--text-dim); margin-top: 1px; }
|
|
1426
|
+
.version-npm-btn {
|
|
1427
|
+
background: transparent;
|
|
1428
|
+
color: var(--accent);
|
|
1429
|
+
border: 1px solid var(--accent);
|
|
1430
|
+
border-radius: 4px;
|
|
1431
|
+
padding: 3px 8px;
|
|
1432
|
+
font-size: 10px;
|
|
1433
|
+
font-family: var(--font-mono, monospace);
|
|
1434
|
+
cursor: pointer;
|
|
1435
|
+
transition: background 0.15s, color 0.15s;
|
|
1436
|
+
}
|
|
1437
|
+
.version-npm-btn:hover { background: var(--accent); color: #fff; }
|
|
1438
|
+
.version-notes-btn:hover { color: var(--accent); border-color: var(--accent); }
|
|
1439
|
+
|
|
1323
1440
|
/* ─────────────────────────────────────────────────────────────────────────────
|
|
1324
1441
|
SOURCES PANEL
|
|
1325
1442
|
───────────────────────────────────────────────────────────────────────────── */
|
|
@@ -1684,7 +1801,7 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1684
1801
|
|
|
1685
1802
|
/* ── Detail Panel Search ───────────────────────────────────────────────────── */
|
|
1686
1803
|
.detail-search-wrap { display: flex; align-items: center; gap: 4px; margin-left: auto; padding: 0 6px; }
|
|
1687
|
-
.detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--
|
|
1804
|
+
.detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--bg2); color: var(--text); border-radius: 3px; outline: none; }
|
|
1688
1805
|
.detail-search-input:focus { border-color: var(--accent); }
|
|
1689
1806
|
.detail-search-count { font-size: 9px; color: var(--text-dim); white-space: nowrap; min-width: 45px; }
|
|
1690
1807
|
.detail-search-nav { background: transparent; border: 1px solid var(--border); color: var(--text-dim); font-size: 9px; width: 18px; height: 18px; border-radius: 3px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
|
|
@@ -1695,13 +1812,19 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1695
1812
|
.detail-search-hl.active { background: rgba(255,213,79,.7); outline: 1px solid rgba(255,213,79,.9); }
|
|
1696
1813
|
|
|
1697
1814
|
/* ── Changelog Modal ───────────────────────────────────────────────────────── */
|
|
1698
|
-
.changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.
|
|
1699
|
-
|
|
1700
|
-
.changelog-
|
|
1815
|
+
.changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); z-index: 9999; display: flex; align-items: center; justify-content: center; animation: changelogFadeIn 0.15s ease-out; }
|
|
1816
|
+
@keyframes changelogFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
1817
|
+
.changelog-modal { background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; width: 520px; max-width: 90vw; max-height: 70vh; display: flex; flex-direction: column; box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.05) inset; animation: changelogSlideIn 0.2s ease-out; }
|
|
1818
|
+
@keyframes changelogSlideIn { from { opacity: 0; transform: translateY(12px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
1819
|
+
.changelog-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--border); background: var(--bg3); border-radius: 12px 12px 0 0; }
|
|
1701
1820
|
.changelog-title { font-size: 13px; font-weight: 700; color: var(--text); }
|
|
1702
|
-
.changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding:
|
|
1703
|
-
.changelog-close:hover { color: var(--text); }
|
|
1704
|
-
.changelog-body { flex: 1; overflow-y: auto; padding:
|
|
1821
|
+
.changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding: 2px 6px; border-radius: 4px; transition: background 0.12s, color 0.12s; }
|
|
1822
|
+
.changelog-close:hover { color: var(--text); background: var(--bg4); }
|
|
1823
|
+
.changelog-body { flex: 1; overflow-y: auto; padding: 18px; font-size: 11px; line-height: 1.7; color: var(--text-mid); }
|
|
1824
|
+
.changelog-body::-webkit-scrollbar { width: 4px; }
|
|
1825
|
+
.changelog-body::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 4px; }
|
|
1826
|
+
.changelog-link { color: var(--accent); text-decoration: none; cursor: pointer; border-bottom: 1px dotted var(--accent); transition: opacity 0.12s; }
|
|
1827
|
+
.changelog-link:hover { opacity: 0.8; }
|
|
1705
1828
|
|
|
1706
1829
|
/* ── Support Button ────────────────────────────────────────────────────────── */
|
|
1707
1830
|
.support-btn { background: linear-gradient(135deg, #ff813f, #ff5e72); color: #fff; border: none; padding: 8px 20px; border-radius: 8px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.15s; letter-spacing: 0.3px; }
|