reactoradar 1.6.3 → 1.6.4
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 +239 -28
- package/main.js +15 -0
- package/package.json +1 -1
- package/preload.js +1 -0
- package/styles.css +21 -0
package/app.js
CHANGED
|
@@ -303,6 +303,17 @@ if (window.electronAPI) {
|
|
|
303
303
|
|
|
304
304
|
// Cmd+F — focus the search input for the active panel
|
|
305
305
|
function _handleFind() {
|
|
306
|
+
// If network detail is open, focus the detail search
|
|
307
|
+
if (state.activePanel === 'network' && state.network.selectedId) {
|
|
308
|
+
const wrap = $('detailSearchWrap');
|
|
309
|
+
const input = $('detailSearchInput');
|
|
310
|
+
if (wrap && input) {
|
|
311
|
+
wrap.style.display = 'flex';
|
|
312
|
+
input.focus();
|
|
313
|
+
input.select();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
306
317
|
const searchMap = {
|
|
307
318
|
console: 'consoleSearch',
|
|
308
319
|
network: 'netSearchInput',
|
|
@@ -366,6 +377,7 @@ function _applyUpdateBanner() {
|
|
|
366
377
|
if (!info) return;
|
|
367
378
|
const { current, latest, autoUpdate } = info;
|
|
368
379
|
const downloaded = state._updateDownloaded;
|
|
380
|
+
const targetVersion = downloaded || latest;
|
|
369
381
|
|
|
370
382
|
const el = $('aboutVersion');
|
|
371
383
|
if (el) {
|
|
@@ -376,39 +388,86 @@ function _applyUpdateBanner() {
|
|
|
376
388
|
}
|
|
377
389
|
}
|
|
378
390
|
|
|
379
|
-
// Remove old
|
|
391
|
+
// Remove old buttons if state changed
|
|
380
392
|
const oldBtn = $('updateBtn');
|
|
381
393
|
if (oldBtn && downloaded && !oldBtn.dataset.isRestart) oldBtn.parentElement?.remove();
|
|
394
|
+
const oldChangelog = $('changelogBtn');
|
|
395
|
+
if (oldChangelog && downloaded && !oldChangelog.dataset.updated) oldChangelog.remove();
|
|
396
|
+
|
|
397
|
+
const aboutEl = document.querySelector('.settings-about');
|
|
398
|
+
if (!aboutEl) return;
|
|
399
|
+
|
|
400
|
+
// Add "What's new?" link
|
|
401
|
+
if (!$('changelogBtn')) {
|
|
402
|
+
const link = document.createElement('div');
|
|
403
|
+
link.style.cssText = 'margin-top:6px;text-align:center';
|
|
404
|
+
link.innerHTML = `<span id="changelogBtn" class="about-link" style="font-size:10px;cursor:pointer" data-updated="${downloaded ? '1' : ''}">What's new in v${targetVersion}?</span>`;
|
|
405
|
+
aboutEl.appendChild(link);
|
|
406
|
+
$('changelogBtn')?.addEventListener('click', () => _showChangelog(targetVersion));
|
|
407
|
+
}
|
|
382
408
|
|
|
383
|
-
// Add update button
|
|
409
|
+
// Add update button
|
|
384
410
|
if (!$('updateBtn')) {
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
btn.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
btn.innerHTML = '<button id="updateBtn" class="tb-btn" style="font-size:11px;padding:6px 16px;opacity:0.7" disabled>Downloading v' + latest + '...</button>';
|
|
399
|
-
aboutEl.appendChild(btn);
|
|
400
|
-
} else {
|
|
401
|
-
// npx/manual — show download link
|
|
402
|
-
btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Download v' + latest + '</button>';
|
|
403
|
-
aboutEl.appendChild(btn);
|
|
404
|
-
$('updateBtn')?.addEventListener('click', () => {
|
|
405
|
-
window.electronAPI?.openExternal('https://github.com/sharanagouda/reactoradar/releases');
|
|
406
|
-
});
|
|
407
|
-
}
|
|
411
|
+
const btn = document.createElement('div');
|
|
412
|
+
btn.style.cssText = 'margin-top:8px;text-align:center';
|
|
413
|
+
if (downloaded) {
|
|
414
|
+
btn.innerHTML = '<button id="updateBtn" data-is-restart="1" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Restart & Update to v' + downloaded + '</button>';
|
|
415
|
+
aboutEl.appendChild(btn);
|
|
416
|
+
$('updateBtn')?.addEventListener('click', () => window.electronAPI?.installUpdate());
|
|
417
|
+
} else if (autoUpdate) {
|
|
418
|
+
btn.innerHTML = '<button id="updateBtn" class="tb-btn" style="font-size:11px;padding:6px 16px;opacity:0.7" disabled>Downloading v' + latest + '...</button>';
|
|
419
|
+
aboutEl.appendChild(btn);
|
|
420
|
+
} else {
|
|
421
|
+
btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Download v' + latest + '</button>';
|
|
422
|
+
aboutEl.appendChild(btn);
|
|
423
|
+
$('updateBtn')?.addEventListener('click', () => window.electronAPI?.openExternal('https://github.com/sharanagouda/reactoradar/releases'));
|
|
408
424
|
}
|
|
409
425
|
}
|
|
410
426
|
}
|
|
411
427
|
|
|
428
|
+
async function _showChangelog(version) {
|
|
429
|
+
// Remove existing modal
|
|
430
|
+
$('changelogModal')?.remove();
|
|
431
|
+
|
|
432
|
+
const modal = document.createElement('div');
|
|
433
|
+
modal.id = 'changelogModal';
|
|
434
|
+
modal.className = 'changelog-modal-overlay';
|
|
435
|
+
modal.innerHTML = `
|
|
436
|
+
<div class="changelog-modal">
|
|
437
|
+
<div class="changelog-header">
|
|
438
|
+
<span class="changelog-title">What's New in v${esc(version)}</span>
|
|
439
|
+
<button class="changelog-close" id="changelogClose">×</button>
|
|
440
|
+
</div>
|
|
441
|
+
<div class="changelog-body" id="changelogBody">
|
|
442
|
+
<div style="color:var(--text-dim);padding:20px;text-align:center">Loading release notes...</div>
|
|
443
|
+
</div>
|
|
444
|
+
</div>`;
|
|
445
|
+
document.body.appendChild(modal);
|
|
446
|
+
|
|
447
|
+
// Close handlers
|
|
448
|
+
$('changelogClose')?.addEventListener('click', () => modal.remove());
|
|
449
|
+
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
|
|
450
|
+
|
|
451
|
+
// Fetch changelog
|
|
452
|
+
try {
|
|
453
|
+
const notes = await window.electronAPI?.fetchChangelog(version);
|
|
454
|
+
const body = $('changelogBody');
|
|
455
|
+
if (body && notes) {
|
|
456
|
+
// Simple markdown-like rendering
|
|
457
|
+
body.innerHTML = notes
|
|
458
|
+
.replace(/^### (.+)$/gm, '<h3 style="color:var(--accent);font-size:12px;font-weight:700;margin:12px 0 6px">$1</h3>')
|
|
459
|
+
.replace(/^## (.+)$/gm, '<h2 style="color:var(--text);font-size:14px;font-weight:700;margin:16px 0 8px">$1</h2>')
|
|
460
|
+
.replace(/^- \*\*(.+?)\*\*(.*)$/gm, '<div style="margin:3px 0;font-size:11px;line-height:1.6"><b style="color:var(--text)">$1</b><span style="color:var(--text-dim)">$2</span></div>')
|
|
461
|
+
.replace(/^- (.+)$/gm, '<div style="margin:3px 0;font-size:11px;line-height:1.6;color:var(--text-mid)">• $1</div>')
|
|
462
|
+
.replace(/`([^`]+)`/g, '<code style="background:var(--bg3);padding:1px 4px;border-radius:3px;color:var(--accent);font-size:10px">$1</code>')
|
|
463
|
+
.replace(/\n\n/g, '<br/>');
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
const body = $('changelogBody');
|
|
467
|
+
if (body) body.innerHTML = '<div style="color:var(--red);padding:20px;text-align:center">Could not fetch release notes</div>';
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
412
471
|
function updateDeviceBanner(service, connected) {
|
|
413
472
|
state.connections[service] = connected;
|
|
414
473
|
const el = $('deviceStatus');
|
|
@@ -1255,6 +1314,13 @@ function initNetworkPanel() {
|
|
|
1255
1314
|
<div class="net-detail-pane" id="netDetailPane">
|
|
1256
1315
|
<div class="net-detail-bar">
|
|
1257
1316
|
<div class="detail-tabs" id="netDetailTabs"></div>
|
|
1317
|
+
<div class="detail-search-wrap" id="detailSearchWrap" style="display:none">
|
|
1318
|
+
<input id="detailSearchInput" class="detail-search-input" placeholder="Search key or value..." />
|
|
1319
|
+
<span id="detailSearchCount" class="detail-search-count"></span>
|
|
1320
|
+
<button class="detail-search-nav" id="detailSearchPrev" title="Previous">▲</button>
|
|
1321
|
+
<button class="detail-search-nav" id="detailSearchNext" title="Next">▼</button>
|
|
1322
|
+
<button class="detail-search-close" id="detailSearchClose" title="Close search">×</button>
|
|
1323
|
+
</div>
|
|
1258
1324
|
<button class="detail-close" id="netDetailClose" title="Close">×</button>
|
|
1259
1325
|
</div>
|
|
1260
1326
|
<div class="detail-content" id="netDetailContent"></div>
|
|
@@ -1408,6 +1474,121 @@ function initNetworkPanel() {
|
|
|
1408
1474
|
// Close detail button
|
|
1409
1475
|
$('netDetailClose').addEventListener('click', closeNetDetail);
|
|
1410
1476
|
|
|
1477
|
+
// Detail panel search
|
|
1478
|
+
let _detailSearchMatches = [];
|
|
1479
|
+
let _detailSearchIdx = -1;
|
|
1480
|
+
|
|
1481
|
+
function _detailSearch() {
|
|
1482
|
+
const term = $('detailSearchInput')?.value?.trim().toLowerCase();
|
|
1483
|
+
const body = $('netDetailContent');
|
|
1484
|
+
if (!body || !term) { _detailClearSearch(); return; }
|
|
1485
|
+
|
|
1486
|
+
// Remove old highlights
|
|
1487
|
+
body.querySelectorAll('.detail-search-hl').forEach(el => {
|
|
1488
|
+
const parent = el.parentNode;
|
|
1489
|
+
parent.replaceChild(document.createTextNode(el.textContent), el);
|
|
1490
|
+
parent.normalize();
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
_detailSearchMatches = [];
|
|
1494
|
+
_detailSearchIdx = -1;
|
|
1495
|
+
|
|
1496
|
+
// Walk all text nodes and highlight matches
|
|
1497
|
+
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null);
|
|
1498
|
+
const textNodes = [];
|
|
1499
|
+
while (walker.nextNode()) textNodes.push(walker.currentNode);
|
|
1500
|
+
|
|
1501
|
+
textNodes.forEach(node => {
|
|
1502
|
+
const text = node.textContent;
|
|
1503
|
+
const lower = text.toLowerCase();
|
|
1504
|
+
if (!lower.includes(term)) return;
|
|
1505
|
+
|
|
1506
|
+
const frag = document.createDocumentFragment();
|
|
1507
|
+
let lastIdx = 0;
|
|
1508
|
+
let idx;
|
|
1509
|
+
while ((idx = lower.indexOf(term, lastIdx)) !== -1) {
|
|
1510
|
+
if (idx > lastIdx) frag.appendChild(document.createTextNode(text.slice(lastIdx, idx)));
|
|
1511
|
+
const hl = document.createElement('span');
|
|
1512
|
+
hl.className = 'detail-search-hl';
|
|
1513
|
+
hl.textContent = text.slice(idx, idx + term.length);
|
|
1514
|
+
_detailSearchMatches.push(hl);
|
|
1515
|
+
frag.appendChild(hl);
|
|
1516
|
+
lastIdx = idx + term.length;
|
|
1517
|
+
}
|
|
1518
|
+
if (lastIdx < text.length) frag.appendChild(document.createTextNode(text.slice(lastIdx)));
|
|
1519
|
+
node.parentNode.replaceChild(frag, node);
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
// Update count
|
|
1523
|
+
const countEl = $('detailSearchCount');
|
|
1524
|
+
if (countEl) countEl.textContent = _detailSearchMatches.length ? `${_detailSearchMatches.length} found` : 'No match';
|
|
1525
|
+
|
|
1526
|
+
// Navigate to first match
|
|
1527
|
+
if (_detailSearchMatches.length) _detailNavTo(0);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function _detailNavTo(idx) {
|
|
1531
|
+
// Remove active highlight from previous
|
|
1532
|
+
if (_detailSearchIdx >= 0 && _detailSearchMatches[_detailSearchIdx]) {
|
|
1533
|
+
_detailSearchMatches[_detailSearchIdx].classList.remove('active');
|
|
1534
|
+
}
|
|
1535
|
+
_detailSearchIdx = idx;
|
|
1536
|
+
const el = _detailSearchMatches[idx];
|
|
1537
|
+
if (!el) return;
|
|
1538
|
+
el.classList.add('active');
|
|
1539
|
+
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1540
|
+
// Update count
|
|
1541
|
+
const countEl = $('detailSearchCount');
|
|
1542
|
+
if (countEl) countEl.textContent = `${idx + 1}/${_detailSearchMatches.length}`;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function _detailClearSearch() {
|
|
1546
|
+
const body = $('netDetailContent');
|
|
1547
|
+
if (body) {
|
|
1548
|
+
body.querySelectorAll('.detail-search-hl').forEach(el => {
|
|
1549
|
+
const parent = el.parentNode;
|
|
1550
|
+
parent.replaceChild(document.createTextNode(el.textContent), el);
|
|
1551
|
+
parent.normalize();
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
_detailSearchMatches = [];
|
|
1555
|
+
_detailSearchIdx = -1;
|
|
1556
|
+
const countEl = $('detailSearchCount');
|
|
1557
|
+
if (countEl) countEl.textContent = '';
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
$('detailSearchInput')?.addEventListener('input', () => {
|
|
1561
|
+
clearTimeout($('detailSearchInput')._debounce);
|
|
1562
|
+
$('detailSearchInput')._debounce = setTimeout(_detailSearch, 200);
|
|
1563
|
+
});
|
|
1564
|
+
$('detailSearchInput')?.addEventListener('keydown', (e) => {
|
|
1565
|
+
if (e.key === 'Enter') {
|
|
1566
|
+
e.preventDefault();
|
|
1567
|
+
if (!_detailSearchMatches.length) return;
|
|
1568
|
+
const next = e.shiftKey
|
|
1569
|
+
? (_detailSearchIdx - 1 + _detailSearchMatches.length) % _detailSearchMatches.length
|
|
1570
|
+
: (_detailSearchIdx + 1) % _detailSearchMatches.length;
|
|
1571
|
+
_detailNavTo(next);
|
|
1572
|
+
}
|
|
1573
|
+
if (e.key === 'Escape') {
|
|
1574
|
+
_detailClearSearch();
|
|
1575
|
+
$('detailSearchWrap').style.display = 'none';
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
$('detailSearchNext')?.addEventListener('click', () => {
|
|
1579
|
+
if (!_detailSearchMatches.length) return;
|
|
1580
|
+
_detailNavTo((_detailSearchIdx + 1) % _detailSearchMatches.length);
|
|
1581
|
+
});
|
|
1582
|
+
$('detailSearchPrev')?.addEventListener('click', () => {
|
|
1583
|
+
if (!_detailSearchMatches.length) return;
|
|
1584
|
+
_detailNavTo((_detailSearchIdx - 1 + _detailSearchMatches.length) % _detailSearchMatches.length);
|
|
1585
|
+
});
|
|
1586
|
+
$('detailSearchClose')?.addEventListener('click', () => {
|
|
1587
|
+
_detailClearSearch();
|
|
1588
|
+
$('detailSearchInput').value = '';
|
|
1589
|
+
$('detailSearchWrap').style.display = 'none';
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1411
1592
|
buildNetHeader();
|
|
1412
1593
|
}
|
|
1413
1594
|
|
|
@@ -1828,14 +2009,38 @@ function closeNetDetail() {
|
|
|
1828
2009
|
);
|
|
1829
2010
|
}
|
|
1830
2011
|
|
|
2012
|
+
function _estimateSize(val) {
|
|
2013
|
+
if (val == null) return 0;
|
|
2014
|
+
if (typeof val === 'string') return val.length;
|
|
2015
|
+
try { return JSON.stringify(val).length; } catch { return 0; }
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function _formatBytes(bytes) {
|
|
2019
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
2020
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
2021
|
+
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1831
2024
|
function renderNetDetailTabs(r) {
|
|
1832
2025
|
const tabs = $('netDetailTabs');
|
|
1833
2026
|
tabs.innerHTML = '';
|
|
1834
|
-
|
|
1835
|
-
|
|
2027
|
+
|
|
2028
|
+
const tabDefs = [
|
|
2029
|
+
{ label: 'Headers', key: 'headers' },
|
|
2030
|
+
{ label: 'Request', key: 'request', sizeFrom: 'requestBody' },
|
|
2031
|
+
{ label: 'Preview', key: 'preview', sizeFrom: 'responseBody' },
|
|
2032
|
+
{ label: 'Response', key: 'response', sizeFrom: 'responseBody' },
|
|
2033
|
+
];
|
|
2034
|
+
|
|
2035
|
+
tabDefs.forEach(({ label, key, sizeFrom }) => {
|
|
1836
2036
|
const btn = document.createElement('button');
|
|
1837
2037
|
btn.className = 'detail-tab' + (r._tab === key ? ' active' : '');
|
|
1838
|
-
|
|
2038
|
+
let text = label;
|
|
2039
|
+
if (sizeFrom && r[sizeFrom]) {
|
|
2040
|
+
const size = _estimateSize(r[sizeFrom]);
|
|
2041
|
+
if (size > 0) text += ` (${_formatBytes(size)})`;
|
|
2042
|
+
}
|
|
2043
|
+
btn.textContent = text;
|
|
1839
2044
|
btn.addEventListener('click', () => {
|
|
1840
2045
|
r._tab = key;
|
|
1841
2046
|
tabs.querySelectorAll('.detail-tab').forEach(b => b.classList.remove('active'));
|
|
@@ -1844,6 +2049,12 @@ function renderNetDetailTabs(r) {
|
|
|
1844
2049
|
});
|
|
1845
2050
|
tabs.appendChild(btn);
|
|
1846
2051
|
});
|
|
2052
|
+
|
|
2053
|
+
// Show search box for Preview/Response tabs
|
|
2054
|
+
const searchWrap = $('detailSearchWrap');
|
|
2055
|
+
if (searchWrap) {
|
|
2056
|
+
searchWrap.style.display = (r._tab === 'preview' || r._tab === 'response' || r._tab === 'headers') ? 'flex' : 'none';
|
|
2057
|
+
}
|
|
1847
2058
|
}
|
|
1848
2059
|
|
|
1849
2060
|
function renderNetDetailContent(r) {
|
package/main.js
CHANGED
|
@@ -588,6 +588,21 @@ function setupIPC() {
|
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
590
|
|
|
591
|
+
ipcMain.handle('fetch-changelog', async (_, version) => {
|
|
592
|
+
return new Promise((resolve) => {
|
|
593
|
+
https.get(`https://api.github.com/repos/sharanagouda/reactoradar/releases/tags/v${version}`, {
|
|
594
|
+
headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
|
|
595
|
+
}, (res) => {
|
|
596
|
+
let data = '';
|
|
597
|
+
res.on('data', d => data += d);
|
|
598
|
+
res.on('end', () => {
|
|
599
|
+
try { resolve(JSON.parse(data).body || 'No release notes available.'); }
|
|
600
|
+
catch { resolve('Could not fetch release notes.'); }
|
|
601
|
+
});
|
|
602
|
+
}).on('error', () => resolve('Could not connect to GitHub.'));
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
591
606
|
ipcMain.on('install-update', () => {
|
|
592
607
|
if (autoUpdater) {
|
|
593
608
|
autoUpdater.quitAndInstall(false, true);
|
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.4",
|
|
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
|
@@ -35,4 +35,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
35
35
|
startNativeLogs: (platform) => ipcRenderer.send('start-native-logs', platform),
|
|
36
36
|
stopNativeLogs: () => ipcRenderer.send('stop-native-logs'),
|
|
37
37
|
detectNativePlatform: () => ipcRenderer.invoke('detect-native-platform'),
|
|
38
|
+
fetchChangelog: (version) => ipcRenderer.invoke('fetch-changelog', version),
|
|
38
39
|
});
|
package/styles.css
CHANGED
|
@@ -1682,6 +1682,27 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1682
1682
|
.native-fatal { background: rgba(255,94,114,.08); }
|
|
1683
1683
|
.native-fatal .native-log-msg { color: var(--red); font-weight: 700; }
|
|
1684
1684
|
|
|
1685
|
+
/* ── Detail Panel Search ───────────────────────────────────────────────────── */
|
|
1686
|
+
.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(--bg1); color: var(--text); border-radius: 3px; outline: none; }
|
|
1688
|
+
.detail-search-input:focus { border-color: var(--accent); }
|
|
1689
|
+
.detail-search-count { font-size: 9px; color: var(--text-dim); white-space: nowrap; min-width: 45px; }
|
|
1690
|
+
.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; }
|
|
1691
|
+
.detail-search-nav:hover { background: var(--bg3); color: var(--text); }
|
|
1692
|
+
.detail-search-close { background: transparent; border: none; color: var(--text-dim); font-size: 12px; cursor: pointer; padding: 0 2px; }
|
|
1693
|
+
.detail-search-close:hover { color: var(--text); }
|
|
1694
|
+
.detail-search-hl { background: rgba(255,213,79,.3); border-radius: 2px; padding: 0 1px; }
|
|
1695
|
+
.detail-search-hl.active { background: rgba(255,213,79,.7); outline: 1px solid rgba(255,213,79,.9); }
|
|
1696
|
+
|
|
1697
|
+
/* ── Changelog Modal ───────────────────────────────────────────────────────── */
|
|
1698
|
+
.changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 9999; display: flex; align-items: center; justify-content: center; }
|
|
1699
|
+
.changelog-modal { background: var(--bg1); border: 1px solid var(--border); border-radius: 10px; width: 520px; max-width: 90vw; max-height: 70vh; display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,.4); }
|
|
1700
|
+
.changelog-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
|
|
1701
|
+
.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: 0 4px; }
|
|
1703
|
+
.changelog-close:hover { color: var(--text); }
|
|
1704
|
+
.changelog-body { flex: 1; overflow-y: auto; padding: 16px; font-size: 11px; line-height: 1.6; color: var(--text-mid); }
|
|
1705
|
+
|
|
1685
1706
|
/* ── Support Button ────────────────────────────────────────────────────────── */
|
|
1686
1707
|
.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; }
|
|
1687
1708
|
.support-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(255,94,114,.3); }
|