Greasy Fork is available in English.
在页面上添加一个可拖动、可折叠、带标签切换和新控件的紫色主题交互式面板。
当前为
// ==UserScript==
// @name 交互式悬浮面板 (紫色主题)
// @namespace http://tampermonkey.net/ // Optional: Change to your namespace (e.g., your website or GitHub)
// @version 2.0
// @description 在页面上添加一个可拖动、可折叠、带标签切换和新控件的紫色主题交互式面板。
// @author Your Name // CHANGE THIS
// @match *://*/* // VERY IMPORTANT: Change this to the specific websites you want the panel on! E.g., *://*.example.com/*
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzhiNWNmNiI+PHBhdGggZD0iTTIxLjkyIDEyLjEzYy0uMS0uMjUtLjI1LS40OC0uNDMtLjY4bC04LTlhMSAxIDAgMCAwLTEuNDggMEwxMiA0LjAxbC0xLjAxLTEuMTRhMSAxIDAgMCAwLTEuNDggMEwyLjc4IDExLjQ0YS45ODUuOTg1IDAgMCAwLS40My42OCAxIDEgMCAwIDAgLjU0IDEuMDhsNi4xLjY4di45M2MwIC41NS40NSAxIDEgMWgzYzEuMSAwIDItLjkgMi0ydi0uNzVsNi4wNy0uNjhhMSAxIDAgMCAwIC41NC0xLjA3ek0xNyA5bDQtNWExIDEgMCAwIDAtMS40MS0xLjQxTDExIDhsMy4yNy0zLjY5YTEgMSAwIDAgMSAxLjQxIDBMxNyA1em0tOSA3djJjMCAuNTUtLjQ1IDEtMSAxcy0xLS40NS0xLTF2LTJoLTJ2MmMwIDEuNjYgMS4zNCAzIDMgM2gyYzEuNjYgMCAzLTEuMzQgMy0zdi0yaDJ2Mi4wMWMwIDEuNjYgMS4zNCAzIDMgM2gxYTkgOSAwIDAgMC0xOCAwYzAgLjU1LjQ1IDEgMSAxaDEzLjU5Yy0uMzYtLjU5LS40MS0xLjI5LS4xMy0xLjk3TDE3IDE2LjgxVjE2aC05em0yLTEyLjM0TDE4Ljk5IDlsLS4wMS4wMS0zLjI3IDMuNjktNC43MS01LjM0ek00IDVhMSA5IDAgMCAwIDE4IDBoLTJhNyA3IDAgMCAxLTE0IDBWNUg0eiIvPjwvc3ZnPg== // Optional icon
// @grant GM_addStyle
// @license MIT // Optional: Choose a license (e.g., MIT, CC0)
// ==/UserScript==
(function() {
'use strict';
// 1. CSS Styles (Copied from the <style> tag)
const panelCSS = `
/* --- Base Styles --- */
/* Avoid styling html, body directly in userscripts if possible, */
/* unless absolutely necessary and tested. */
/* Instead, focus styles on the panel itself. */
/* --- Panel Container --- */
.interactive-panel-userscript { /* Added -userscript suffix to avoid potential conflicts */
position: fixed;
top: 60px;
left: 60px;
width: 420px; /* Initial width */
max-height: 650px; /* Max height */
background: rgba(250, 245, 255, 0.75); /* Adjusted alpha */
backdrop-filter: blur(14px);
border-radius: 18px;
box-shadow: 0 8px 25px rgba(109, 40, 217, 0.15); /* Slightly stronger default shadow */
display: flex;
flex-direction: column;
overflow: hidden;
resize: both;
transition: box-shadow 0.3s ease, opacity 0.3s ease; /* Added opacity transition */
z-index: 99999; /* Ensure high z-index */
min-width: 250px;
min-height: 50px;
border: 1px solid rgba(167, 139, 250, 0.3);
color: #374151; /* Default text color */
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
font-size: 14px; /* Base font size for content */
}
.interactive-panel-userscript:hover {
box-shadow: 0 12px 30px rgba(109, 40, 217, 0.22);
}
/* --- Collapsed State --- */
.interactive-panel-userscript.panel-collapsed {
overflow: hidden !important;
resize: none !important;
opacity: 0.9;
/* height is set by JS */
}
.interactive-panel-userscript.panel-collapsed .tab-navigation,
.interactive-panel-userscript.panel-collapsed .tab-content {
display: none;
}
/* --- Panel Header --- */
.interactive-panel-userscript .panel-header {
background: linear-gradient(to right, #6d28d9, #8b5cf6);
color: #fff;
padding: 10px 15px;
cursor: move;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
font-size: 1rem; /* 16px */
font-weight: 600;
box-sizing: border-box;
min-height: 48px;
flex-shrink: 0;
}
.interactive-panel-userscript .panel-title-area {
display: flex;
align-items: center;
gap: 10px;
flex-grow: 1;
flex-shrink: 1;
min-width: 50px;
overflow: hidden;
}
.interactive-panel-userscript .panel-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.interactive-panel-userscript .panel-status-display {
font-size: 0.8rem;
font-weight: 400;
padding: 2px 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100px;
transition: background-color 0.3s ease; /* Smooth transition for status */
}
.interactive-panel-userscript .panel-status-display:empty {
display: none; /* Hide status if empty */
}
.interactive-panel-userscript .panel-controls {
display: flex;
align-items: center;
gap: 5px;
flex-shrink: 0;
}
.interactive-panel-userscript .panel-header-button {
background: transparent;
border: none;
color: #ede9fe;
font-size: 1.1rem;
cursor: pointer;
transition: transform 0.2s ease, color 0.2s ease;
padding: 0 5px;
line-height: 1;
border-radius: 4px;
}
.interactive-panel-userscript .panel-header-button:hover {
transform: scale(1.15);
color: #ffffff;
}
.interactive-panel-userscript .panel-header-button:active {
transform: scale(1.05);
}
/* --- Tab Navigation --- */
.interactive-panel-userscript .tab-navigation {
display: flex;
background: rgba(237, 233, 254, 0.8);
border-bottom: 1px solid #c4b5fd;
flex-shrink: 0;
padding: 3px 5px 0;
}
.interactive-panel-userscript .tab-button {
flex: 1;
padding: 9px 5px;
background: transparent;
border: none;
font-size: 0.875rem; /* 14px */
cursor: pointer;
transition: background 0.3s, color 0.3s, border-color 0.3s;
color: #5b21b6;
white-space: nowrap;
border-bottom: 3px solid transparent;
margin: 0 2px;
border-radius: 6px 6px 0 0;
outline: none; /* Remove default focus outline */
}
.interactive-panel-userscript .tab-button:focus-visible {
box-shadow: 0 0 0 2px rgba(109, 40, 217, 0.4); /* Custom focus style */
}
.interactive-panel-userscript .tab-button:hover {
background: rgba(196, 181, 253, 0.4);
color: #4c1d95;
}
.interactive-panel-userscript .tab-button.active {
background: linear-gradient(to right, #a78bfa, #c4b5fd);
color: #3730a3;
font-weight: 600;
border-bottom-color: #6d28d9;
}
/* --- Tab Content --- */
.interactive-panel-userscript .tab-content {
flex: 1;
overflow-y: auto;
padding: 15px 20px;
background: transparent; /* Let panel background show through */
min-height: 100px;
color: #374151;
}
.interactive-panel-userscript .tab-panel {
display: none;
animation: ipFadeIn 0.4s ease forwards; /* Prefixed animation name */
}
.interactive-panel-userscript .tab-panel.active {
display: block;
}
@keyframes ipFadeIn { /* Prefixed animation name */
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* --- Content Specific Styles (Purple Theme) --- */
.interactive-panel-userscript .tab-panel h2 {
color: #6d28d9;
margin-top: 0;
margin-bottom: 15px;
border-bottom: 1px solid #ddd6fe;
padding-bottom: 5px;
font-size: 1.1rem; /* Slightly larger heading */
}
.interactive-panel-userscript .tab-panel strong {
color: #7c3aed;
font-weight: 600; /* Ensure strong is bold */
}
.interactive-panel-userscript .memory-area ol { position: relative; margin-left: 15px; padding-left: 25px; list-style: none; }
.interactive-panel-userscript .memory-area ol::before { content: ""; position: absolute; left: 8px; top: 5px; bottom: 5px; width: 2px; background: #a78bfa; border-radius: 1px; }
.interactive-panel-userscript .memory-area ol li { position: relative; padding: 10px 0; font-size: 0.9em; }
.interactive-panel-userscript .memory-area ol li::before { content: ""; position: absolute; left: -21px; top: 50%; transform: translateY(-50%); width: 10px; height: 10px; background: #8b5cf6; border-radius: 50%; border: 2px solid #f3e8ff; }
.interactive-panel-userscript .status-bar ul { padding-left: 20px; margin: 10px 0; list-style-type: none; /* Remove default bullets */ }
.interactive-panel-userscript .status-bar ul ul { padding-left: 20px; }
.interactive-panel-userscript .status-bar li { margin-bottom: 6px; position: relative; padding-left: 15px; font-size: 0.9em;}
.interactive-panel-userscript .status-bar li::before { /* Custom bullet */
content: '•';
position: absolute;
left: 0;
color: #a78bfa;
font-weight: bold;
font-size: 1.1em;
line-height: 1;
}
.interactive-panel-userscript .options-area ol { list-style: none; padding: 0; margin: 0; }
.interactive-panel-userscript .options-area li {
background: rgba(167, 139, 250, 0.15);
margin-bottom: 10px;
padding: 12px 15px;
border-radius: 8px;
cursor: pointer;
transition: background 0.3s, transform 0.2s, box-shadow 0.3s;
border-left: 4px solid #a78bfa;
color: #4c1d95;
font-size: 0.9em;
}
.interactive-panel-userscript .options-area li:hover {
background: rgba(167, 139, 250, 0.25);
transform: translateY(-2px);
box-shadow: 0 3px 8px rgba(109, 40, 217, 0.08);
border-left-color: #8b5cf6;
}
.interactive-panel-userscript .thinking-area ul { list-style: none; margin: 0; padding: 0 0 0 15px; color: #5b21b6; }
.interactive-panel-userscript .thinking-area li { margin-bottom: 8px; position: relative; padding-left: 18px; font-size: 0.9em;}
.interactive-panel-userscript .thinking-area li::before { /* Custom thinking bullet */
content: '💡'; /* Or '🤔' */
position: absolute;
left: 0;
top: 1px;
}
.interactive-panel-userscript .text-body p { margin: 10px 0; line-height: 1.6; }
.interactive-panel-userscript .speech { color: #7c3aed; font-weight: bold; }
.interactive-panel-userscript .mentality { color: #9333ea; font-style: italic; }
.interactive-panel-userscript .closeup { color: #5b21b6; text-decoration: underline; text-decoration-color: #a78bfa; text-decoration-thickness: 1px; text-underline-offset: 2px; }
/* Scrollbar styling */
.interactive-panel-userscript .tab-content::-webkit-scrollbar {
width: 8px;
}
.interactive-panel-userscript .tab-content::-webkit-scrollbar-track {
background: rgba(237, 233, 254, 0.5); /* Light violet track */
border-radius: 4px;
}
.interactive-panel-userscript .tab-content::-webkit-scrollbar-thumb {
background-color: #c4b5fd;
border-radius: 4px;
border: 2px solid transparent; /* Make thumb seem smaller */
background-clip: content-box;
}
.interactive-panel-userscript .tab-content::-webkit-scrollbar-thumb:hover {
background-color: #a78bfa;
}
`; // End of CSS
// 2. Inject CSS
GM_addStyle(panelCSS);
// 3. Panel HTML Structure (Copied from the <body>, inside the main div)
const panelHTML = `
<div class="panel-header">
<div class="panel-title-area">
<div class="panel-title">交互面板</div>
<span class="panel-status-display" id="panelStatusUserscript">就绪</span> <!-- Changed ID -->
</div>
<div class="panel-controls">
<button class="panel-header-button panel-refresh" title="刷新" aria-label="刷新">🔄</button>
<button class="panel-header-button panel-simplify" title="简化回复" aria-label="简化回复">✨</button>
<button class="panel-header-button panel-toggle" title="折叠/展开" aria-label="折叠/展开">–</button>
</div>
</div>
<div class="tab-navigation">
<button class="tab-button active" data-tab-target="text">文本内容</button>
<button class="tab-button" data-tab-target="thinking">思考区</button>
<button class="tab-button" data-tab-target="status">状态信息</button>
<button class="tab-button" data-tab-target="memory">记忆</button>
<button class="tab-button" data-tab-target="options">选项</button>
</div>
<div class="tab-content">
<div class="tab-panel active" data-tab-panel="text" role="tabpanel">
<div class="text-body">
<h2>文章标题示例</h2>
<p>这是一段示例文本内容,展示了基础的段落样式。您可以根据需要填充实际内容。</p>
<p>这是一个对话:<span class="speech">“你好,沈小姐。这块玉佩似乎对你很重要?”</span></p>
<p>这是一个心理活动:<span class="mentality">(他怎么会知道这玉佩... 难道是母亲那边的人?)</span></p>
<p>这是一个特写:<span class="closeup">「玉佩在月光下泛着温润的光泽,奇特的纹样若隐若现。」</span></p>
<p>更多内容可以添加在这里,滚动条会在内容超出时出现。</p>
</div>
</div>
<div class="tab-panel" data-tab-panel="thinking" role="tabpanel">
<div class="thinking-area">
<h2>思考过程</h2>
<ul>
<li>目标:与沈知微交互,探查玉佩信息。</li>
<li>初步观察:她对玉佩反应强烈,可能认识。</li>
<li>风险评估:直接询问可能引起警惕,甚至冲突。</li>
<li>策略1:试探性询问,观察反应。</li>
<li>策略2:若遇危险,利用环境脱身(假山、水域)。</li>
<li>当前决定:采用策略1,言语温和,保持距离。</li>
</ul>
</div>
</div>
<div class="tab-panel" data-tab-panel="status" role="tabpanel">
<div class="status-bar">
<h2>场景信息</h2>
<ul>
<li><strong>时间:</strong> 永昌七年四月初三,戌时三刻</li>
<li><strong>地点:</strong> 沈府西园·漱玉水榭</li>
<li><strong>环境:</strong> 月光明亮,微风,水声潺潺,垂柳依依</li>
</ul>
<h2>用户状态</h2>
<ul>
<li><strong>身份:</strong> 未知潜入者</li>
<li><strong>位置与姿势:</strong> 立于水榭栏杆旁,面向垂柳下的沈知微</li>
<li><strong>当前着装:</strong> 深青色劲装,腰间悬空剑鞘</li>
<li><strong>持有物:</strong> 特殊纹样玉佩(右手)</li>
</ul>
<h2>主要交互角色</h2>
<ul>
<li><strong>姓名:</strong> 沈知微(沈府三小姐)</li>
<li><strong>当前状态:</strong> 惊疑不定,强作镇定,略带警惕</li>
<li><strong>位置与姿势:</strong> 站在垂柳下,左手提灯笼,右手微握</li>
<li><strong>着装细节:</strong>
<ul>
<li><strong>外衣/上装:</strong> 鹅黄纱衫</li>
<li><strong>下装/裙:</strong> 月白百褶裙</li>
<li><strong>内衣裤:</strong> 杏色主腰(可见领缘)</li>
<li><strong>配饰:</strong> 珍珠耳环,发间碧玉簪</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="tab-panel" data-tab-panel="memory" role="tabpanel">
<div class="memory-area">
<h2>短期记忆</h2>
<ol>
<li><strong>刚刚 | 水榭 | 沈知微:</strong> 她看到玉佩后明显愣住,眼神复杂。</li>
<li><strong>片刻前 | 西园:</strong> 避开巡逻护卫,潜入水榭区域。</li>
</ol>
<h2>长期记忆</h2>
<ol>
<li><strong>永昌年间 | 任务简报:</strong> 沈家与某个失落的组织有关联,关键信物是特殊纹样玉佩。</li>
<li><strong>数年前 | 师门:</strong> 师傅曾提及玉佩的重要性,关系重大。</li>
<li><strong>童年 | 模糊印象:</strong> 似乎见过类似的纹样,但记忆不清。</li>
</ol>
</div>
</div>
<div class="tab-panel" data-tab-panel="options" role="tabpanel">
<div class="options-area">
<h2>当前选项</h2>
<ol>
<li>[将玉佩收入怀中,压低声音:"姑娘认错人了"]</li>
<li>[举起玉佩逼近:"你认识这物件?说说它的来历"]</li>
<li>[突然揽住她的腰跃入假山后,耳语:"别出声,有人来了"]</li>
<li>[温和询问:"姑娘深夜在此,可是遗落了什么?在下或可帮忙。"]</li>
</ol>
</div>
</div>
</div>
`; // End of HTML
// 4. Create and Inject Panel HTML Element
const panelDiv = document.createElement('div');
panelDiv.className = 'interactive-panel-userscript'; // Use the suffixed class name
panelDiv.innerHTML = panelHTML;
document.body.appendChild(panelDiv);
// 5. JavaScript Logic (Copied from the <script> tag and adapted)
// Use 'panelDiv' as the main container reference.
const panel = panelDiv; // Use the dynamically created div
const header = panel.querySelector('.panel-header');
const toggleButton = panel.querySelector('.panel-toggle');
const refreshButton = panel.querySelector('.panel-refresh');
const simplifyButton = panel.querySelector('.panel-simplify');
const panelStatusDisplay = panel.querySelector('#panelStatusUserscript'); // Use updated ID
const tabButtons = panel.querySelectorAll('.tab-button');
const tabPanels = panel.querySelectorAll('.tab-panel');
const tabNavigation = panel.querySelector('.tab-navigation');
const tabContent = panel.querySelector('.tab-content');
const optionsList = panel.querySelector('.options-area ol'); // Get options list for delegation
const animationDuration = 300;
let statusTimeout = null; // To manage status reset timer
// --- Status Update Function ---
function updateStatus(message, duration = 2000) {
if (!panelStatusDisplay) return; // Guard clause
panelStatusDisplay.textContent = message;
// Clear previous timer if any
if (statusTimeout) {
clearTimeout(statusTimeout);
}
// Optionally reset status after a delay
if (duration > 0) {
statusTimeout = setTimeout(() => {
// Reset only if the current message is the one we set
if (panelStatusDisplay.textContent === message) {
panelStatusDisplay.textContent = '就绪';
}
statusTimeout = null;
}, duration);
}
}
// --- Dragging Logic ---
let isDragging = false, startX, startY, startLeft, startTop;
if (header) { // Check if header exists
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.panel-header-button')) return; // Ignore button clicks
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
// Prevent text selection during drag
document.body.style.userSelect = 'none';
document.body.style.cursor = 'move';
panel.style.willChange = 'top, left';
panel.style.transition = 'none'; // Disable transitions during drag for smoothness
});
} // End if(header)
// Attach mousemove and mouseup to document to capture events outside the panel
document.addEventListener('mousemove', (e) => {
if (!isDragging || !panel) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
// Boundary checks using viewport dimensions
const newLeft = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, startLeft + dx));
const newTop = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, startTop + dy));
panel.style.left = `${newLeft}px`;
panel.style.top = `${newTop}px`;
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
document.body.style.userSelect = '';
document.body.style.cursor = '';
if (panel) { // Check panel exists
panel.style.willChange = 'auto';
panel.style.transition = ''; // Re-enable CSS transitions
}
}
});
// --- Collapse/Expand Logic ---
if (toggleButton && panel && header) { // Check elements exist
toggleButton.addEventListener('click', () => {
const isCollapsed = panel.classList.contains('panel-collapsed');
const headerHeight = header.offsetHeight;
panel.style.transition = `height ${animationDuration}ms ease, opacity ${animationDuration}ms ease`;
panel.style.willChange = 'height, opacity';
if (isCollapsed) {
// --- Expand ---
const originalWidth = panel.dataset.originalWidth || '420px';
const originalHeight = panel.dataset.originalHeight || '500px';
panel.style.width = originalWidth;
panel.style.height = originalHeight;
// Opacity handled by class removal below
panel.classList.remove('panel-collapsed');
toggleButton.textContent = '–';
toggleButton.title = '折叠';
toggleButton.setAttribute('aria-label', '折叠面板');
setTimeout(() => {
panel.style.transition = '';
panel.style.resize = 'both';
panel.style.willChange = 'auto';
// Remove fixed height if it was a default, allow content flow
if (!panel.dataset.originalHeight) panel.style.height = '';
}, animationDuration);
updateStatus('已展开', 1500);
} else {
// --- Collapse ---
panel.dataset.originalWidth = `${panel.offsetWidth}px`;
panel.dataset.originalHeight = `${panel.offsetHeight}px`;
panel.style.width = `${panel.offsetWidth}px`; // Fix width explicitly
panel.style.height = `${headerHeight}px`;
panel.style.resize = 'none';
panel.classList.add('panel-collapsed'); // Opacity handled by class
toggleButton.textContent = '+';
toggleButton.title = '展开';
toggleButton.setAttribute('aria-label', '展开面板');
setTimeout(() => {
panel.style.transition = '';
panel.style.willChange = 'auto';
}, animationDuration);
updateStatus('已折叠', 1500);
}
});
} // End if(toggleButton)
// --- Button Click Handlers ---
if (refreshButton) {
refreshButton.addEventListener('click', () => {
console.log('Refresh button clicked');
updateStatus('刷新中...', 1000);
// Add actual refresh logic here
setTimeout(() => {
updateStatus('刷新完成', 1500);
}, 1000);
});
}
if (simplifyButton) {
simplifyButton.addEventListener('click', () => {
console.log('Simplify button clicked');
updateStatus('简化中...', 1000);
// Add actual simplify logic here
setTimeout(() => {
updateStatus('简化完成', 1500);
}, 1000);
});
}
// --- Option Click Handler (using event delegation) ---
if (optionsList) {
optionsList.addEventListener('click', (e) => {
// Check if the clicked element is an LI directly inside the OL
if (e.target.tagName === 'LI' && e.target.parentElement === optionsList) {
// Try to find the option number (if they follow a pattern like starting with '[')
const text = e.target.textContent.trim();
const match = text.match(/^\[(\d+)\]/); // Example: Check for "[1] Option text"
const optionNumber = match ? match[1] : text.substring(0, 20) + '...'; // Fallback to text snippet
console.log(`Option clicked: ${text}`);
updateStatus(`选择: ${optionNumber}`, 2000);
// Add logic to handle the selected option here
// You might want to pass 'e.target' or 'text' to another function
}
});
} else {
// Fallback or alternative if optionsList isn't found or structure changes
// Could add listeners to individual items if needed, but delegation is better.
const optionItems = panel.querySelectorAll('.options-area li');
optionItems.forEach((item, index) => {
item.addEventListener('click', () => handleOptionClickFallback(index + 1, item.textContent));
});
}
function handleOptionClickFallback(optionNumber, text) {
console.log(`Option ${optionNumber} clicked: ${text}`);
updateStatus(`选择选项 ${optionNumber}`, 2000);
}
// --- Tab Switching Logic ---
tabButtons.forEach(button => {
button.addEventListener('click', () => {
if (!panel || panel.classList.contains('panel-collapsed')) {
return;
}
const target = button.getAttribute('data-tab-target');
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
tabPanels.forEach(p => {
p.classList.remove('active');
p.style.display = 'none';
});
const targetPanel = panel.querySelector(`.tab-panel[data-tab-panel="${target}"]`);
if (targetPanel) {
targetPanel.style.display = 'block';
void targetPanel.offsetWidth; // Force reflow for animation
targetPanel.classList.add('active');
}
});
});
// --- Initial State Setup Function ---
function initializePanel() {
if (!panel || !header) return; // Ensure elements exist before setup
const initialActiveButton = panel.querySelector('.tab-button.active');
if (initialActiveButton) {
const initialTarget = initialActiveButton.getAttribute('data-tab-target');
const initialActivePanel = panel.querySelector(`.tab-panel[data-tab-panel="${initialTarget}"]`);
if (initialActivePanel) {
tabPanels.forEach(p => p.style.display = 'none');
initialActivePanel.style.display = 'block';
initialActivePanel.classList.add('active');
}
} else {
// Default to first tab if none are active
const firstButton = panel.querySelector('.tab-button');
const firstPanel = panel.querySelector('.tab-panel');
if(firstButton && firstPanel){
tabButtons.forEach(btn => btn.classList.remove('active'));
tabPanels.forEach(p => { p.classList.remove('active'); p.style.display = 'none'; });
firstButton.classList.add('active');
firstPanel.classList.add('active');
firstPanel.style.display = 'block';
}
}
if (panel.classList.contains('panel-collapsed')) {
panel.style.resize = 'none';
panel.style.height = `${header.offsetHeight}px`;
if(toggleButton) {
toggleButton.textContent = '+';
toggleButton.title = '展开';
toggleButton.setAttribute('aria-label', '展开面板');
}
} else {
panel.style.resize = 'both';
if(toggleButton) {
toggleButton.textContent = '–';
toggleButton.title = '折叠';
toggleButton.setAttribute('aria-label', '折叠面板');
}
// Don't set initial height unless collapsed, let CSS/content decide
// panel.style.height = '';
}
updateStatus('面板已加载', 2500); // Slightly longer initial message
}
// --- Run Initialization ---
// User scripts often run after DOMContentLoaded by default,
// but wrapping in a check or small delay can sometimes help ensure
// the target page's styles/scripts are settled.
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initializePanel();
} else {
document.addEventListener('DOMContentLoaded', initializePanel);
}
})(); // End of IIFE wrapper