Spaces:
Sleeping
Sleeping
NeonRyan commited on
Add User Guide (#53)
Browse files* Draft 1- Enhance UI with User Guide and Sidebar Improvements
- Added UserGuide component to App for global help access.
- Updated Sidebar component to replace icons and improve button styles.
- Introduced new chat button with updated design and functionality.
- Enhanced ChatPage with a help button to open the user guide.
- Improved CSS styles for better layout and user experience.
* Enhance User Guide with Advisor Information
- Updated UserGuide component to include advisor data from app configuration.
- Modified user guide content to dynamically display the number of advisors and their descriptions.
- Replaced static advisor list with a generated list based on available advisors.
* Fixed Help Circle error
- phd-advisor-frontend/src/App.js +3 -0
- phd-advisor-frontend/src/components/UserGuide.js +110 -0
- phd-advisor-frontend/src/data/userGuide.js +143 -0
- phd-advisor-frontend/src/pages/ChatPage.js +12 -1
- phd-advisor-frontend/src/styles/ChatPage.css +26 -0
- phd-advisor-frontend/src/styles/UserGuide.css +281 -0
phd-advisor-frontend/src/App.js
CHANGED
|
@@ -5,6 +5,7 @@ import HomePage from './pages/HomePage';
|
|
| 5 |
import ChatPage from './pages/ChatPage';
|
| 6 |
import AuthPage from './pages/AuthPage';
|
| 7 |
import CanvasPage from './pages/CanvasPage';
|
|
|
|
| 8 |
import './styles/components.css';
|
| 9 |
|
| 10 |
function App() {
|
|
@@ -97,6 +98,8 @@ function App() {
|
|
| 97 |
onSignOut={handleSignOut}
|
| 98 |
/>
|
| 99 |
)}
|
|
|
|
|
|
|
| 100 |
</div>
|
| 101 |
</ThemeProvider>
|
| 102 |
</AppConfigProvider>
|
|
|
|
| 5 |
import ChatPage from './pages/ChatPage';
|
| 6 |
import AuthPage from './pages/AuthPage';
|
| 7 |
import CanvasPage from './pages/CanvasPage';
|
| 8 |
+
import UserGuide from './components/UserGuide';
|
| 9 |
import './styles/components.css';
|
| 10 |
|
| 11 |
function App() {
|
|
|
|
| 98 |
onSignOut={handleSignOut}
|
| 99 |
/>
|
| 100 |
)}
|
| 101 |
+
{/* Global help center — listens for the 'open-user-guide' event */}
|
| 102 |
+
<UserGuide />
|
| 103 |
</div>
|
| 104 |
</ThemeProvider>
|
| 105 |
</AppConfigProvider>
|
phd-advisor-frontend/src/components/UserGuide.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import ReactDOM from 'react-dom';
|
| 3 |
+
import ReactMarkdown from 'react-markdown';
|
| 4 |
+
import remarkGfm from 'remark-gfm';
|
| 5 |
+
import * as LucideIcons from 'lucide-react';
|
| 6 |
+
import { X, Search, ChevronRight, BookOpen } from 'lucide-react';
|
| 7 |
+
import { useAppConfig } from '../contexts/AppConfigContext';
|
| 8 |
+
import { userGuideTopics } from '../data/userGuide';
|
| 9 |
+
import '../styles/UserGuide.css';
|
| 10 |
+
|
| 11 |
+
const UserGuide = () => {
|
| 12 |
+
const { config, advisors } = useAppConfig();
|
| 13 |
+
const [isOpen, setIsOpen] = useState(false);
|
| 14 |
+
const [activeId, setActiveId] = useState(userGuideTopics[0].id);
|
| 15 |
+
const [search, setSearch] = useState('');
|
| 16 |
+
|
| 17 |
+
useEffect(() => {
|
| 18 |
+
const open = () => setIsOpen(true);
|
| 19 |
+
window.addEventListener('open-user-guide', open);
|
| 20 |
+
return () => window.removeEventListener('open-user-guide', open);
|
| 21 |
+
}, []);
|
| 22 |
+
|
| 23 |
+
useEffect(() => {
|
| 24 |
+
if (!isOpen) return;
|
| 25 |
+
const onKey = (e) => e.key === 'Escape' && setIsOpen(false);
|
| 26 |
+
window.addEventListener('keydown', onKey);
|
| 27 |
+
return () => window.removeEventListener('keydown', onKey);
|
| 28 |
+
}, [isOpen]);
|
| 29 |
+
|
| 30 |
+
const appName = config?.app?.title || 'the app';
|
| 31 |
+
const advisorEntries = Object.values(advisors || {});
|
| 32 |
+
const advisorCount = advisorEntries.length;
|
| 33 |
+
const advisorList = advisorEntries
|
| 34 |
+
.map((a) => `- **${a.name}:** ${a.description || a.role || ''}`.trimEnd())
|
| 35 |
+
.join('\n');
|
| 36 |
+
const q = search.toLowerCase().trim();
|
| 37 |
+
const filteredTopics = q
|
| 38 |
+
? userGuideTopics.filter(t =>
|
| 39 |
+
t.title.toLowerCase().includes(q) || t.content.toLowerCase().includes(q))
|
| 40 |
+
: userGuideTopics;
|
| 41 |
+
const activeTopic = userGuideTopics.find(t => t.id === activeId) || userGuideTopics[0];
|
| 42 |
+
|
| 43 |
+
if (!isOpen) return null;
|
| 44 |
+
|
| 45 |
+
return ReactDOM.createPortal(
|
| 46 |
+
<div className="ug-overlay" onClick={(e) => e.target === e.currentTarget && setIsOpen(false)}>
|
| 47 |
+
<div className="ug-modal" role="dialog" aria-label="User Guide">
|
| 48 |
+
{/* Header */}
|
| 49 |
+
<div className="ug-header">
|
| 50 |
+
<div className="ug-title">
|
| 51 |
+
<span className="ug-title-icon">
|
| 52 |
+
<BookOpen size={18} />
|
| 53 |
+
</span>
|
| 54 |
+
<span>User Guide</span>
|
| 55 |
+
</div>
|
| 56 |
+
<button className="ug-close" onClick={() => setIsOpen(false)} aria-label="Close">
|
| 57 |
+
<X size={20} />
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<div className="ug-body">
|
| 62 |
+
{/* Sidebar / TOC */}
|
| 63 |
+
<aside className="ug-sidebar">
|
| 64 |
+
<div className="ug-search">
|
| 65 |
+
<Search size={14} />
|
| 66 |
+
<input
|
| 67 |
+
type="text"
|
| 68 |
+
placeholder="Search the guide…"
|
| 69 |
+
value={search}
|
| 70 |
+
onChange={(e) => setSearch(e.target.value)}
|
| 71 |
+
/>
|
| 72 |
+
</div>
|
| 73 |
+
<nav className="ug-toc">
|
| 74 |
+
{filteredTopics.length === 0 && (
|
| 75 |
+
<div className="ug-empty">No matches</div>
|
| 76 |
+
)}
|
| 77 |
+
{filteredTopics.map((t) => {
|
| 78 |
+
const TopicIcon = LucideIcons[t.icon] || BookOpen;
|
| 79 |
+
return (
|
| 80 |
+
<button
|
| 81 |
+
key={t.id}
|
| 82 |
+
className={`ug-toc-item ${t.id === activeId ? 'active' : ''}`}
|
| 83 |
+
onClick={() => setActiveId(t.id)}
|
| 84 |
+
>
|
| 85 |
+
<TopicIcon size={16} />
|
| 86 |
+
<span>{t.title}</span>
|
| 87 |
+
<ChevronRight size={14} className="ug-toc-arrow" />
|
| 88 |
+
</button>
|
| 89 |
+
);
|
| 90 |
+
})}
|
| 91 |
+
</nav>
|
| 92 |
+
</aside>
|
| 93 |
+
|
| 94 |
+
{/* Content */}
|
| 95 |
+
<main className="ug-content" key={activeTopic.id}>
|
| 96 |
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
| 97 |
+
{activeTopic.content
|
| 98 |
+
.replace(/\{\{appName\}\}/g, appName)
|
| 99 |
+
.replace(/\{\{advisorCount\}\}/g, String(advisorCount))
|
| 100 |
+
.replace(/\{\{advisorList\}\}/g, advisorList)}
|
| 101 |
+
</ReactMarkdown>
|
| 102 |
+
</main>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</div>,
|
| 106 |
+
document.body
|
| 107 |
+
);
|
| 108 |
+
};
|
| 109 |
+
|
| 110 |
+
export default UserGuide;
|
phd-advisor-frontend/src/data/userGuide.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// User Guide content. Content can be overridden per-application by replacing
|
| 2 |
+
// these strings with values pulled from the backend config in the future.
|
| 3 |
+
// Use {{appName}} as a placeholder. It gets replaced at render time.
|
| 4 |
+
|
| 5 |
+
export const userGuideTopics = [
|
| 6 |
+
{
|
| 7 |
+
id: 'getting-started',
|
| 8 |
+
title: 'Getting Started',
|
| 9 |
+
icon: 'Sparkles',
|
| 10 |
+
content: `# Welcome to {{appName}}
|
| 11 |
+
|
| 12 |
+
{{appName}} is your AI-powered academic guidance system. A panel of specialized AI advisors gives you diverse perspectives on your research, writing, methodology, and more.
|
| 13 |
+
|
| 14 |
+
## Your first steps
|
| 15 |
+
1. **Start a new chat** using the pencil icon next to the search bar
|
| 16 |
+
2. **Type a question.** Anything about your research, methodology, or PhD journey
|
| 17 |
+
3. **Read multiple advisor responses.** Each persona brings a different lens
|
| 18 |
+
4. **Reply to a specific advisor** to dig deeper into their perspective
|
| 19 |
+
|
| 20 |
+
## Need help?
|
| 21 |
+
You can return to this guide anytime by clicking the **?** icon in the header.`,
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
id: 'advisors',
|
| 25 |
+
title: 'Your Advisors',
|
| 26 |
+
icon: 'GraduationCap',
|
| 27 |
+
content: `# Your Advisors
|
| 28 |
+
|
| 29 |
+
{{appName}} comes with {{advisorCount}} specialized advisor personas. Each one is tuned with a different perspective and area of expertise.
|
| 30 |
+
|
| 31 |
+
## Available advisors
|
| 32 |
+
{{advisorList}}
|
| 33 |
+
|
| 34 |
+
## Seeing who's available
|
| 35 |
+
Click the **"X Advisors"** dropdown in the top right of the chat to see all of your advisors and their current status.`,
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
id: 'conversations',
|
| 39 |
+
title: 'Conversations & Replies',
|
| 40 |
+
icon: 'MessageCircle',
|
| 41 |
+
content: `# Conversations & Replies
|
| 42 |
+
|
| 43 |
+
## Asking a question
|
| 44 |
+
Type into the chat box at the bottom. All advisors will respond with their unique perspective.
|
| 45 |
+
|
| 46 |
+
## Replying to a specific advisor
|
| 47 |
+
Click on any advisor's response to **reply directly to them**. This continues the conversation with just that persona, letting you go deeper on their specific angle.
|
| 48 |
+
|
| 49 |
+
## Expanding a response
|
| 50 |
+
Some responses include an **"Expand"** action to ask the advisor to elaborate further with more detail.
|
| 51 |
+
|
| 52 |
+
## Tips
|
| 53 |
+
- Be specific. The more context, the better the advice
|
| 54 |
+
- Ask follow-up questions to refine the response
|
| 55 |
+
- Different advisors will sometimes disagree, and that's a feature, not a bug`,
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
id: 'documents',
|
| 59 |
+
title: 'Uploading Documents',
|
| 60 |
+
icon: 'Paperclip',
|
| 61 |
+
content: `# Uploading Documents
|
| 62 |
+
|
| 63 |
+
You can attach **PDFs, Word documents, and text files** to give your advisors context.
|
| 64 |
+
|
| 65 |
+
## How it works
|
| 66 |
+
1. Click the paperclip icon in the chat input
|
| 67 |
+
2. Select your file
|
| 68 |
+
3. Wait for it to process
|
| 69 |
+
4. Ask a question, and your advisors will reference the document
|
| 70 |
+
|
| 71 |
+
## What can it handle?
|
| 72 |
+
- Research papers (PDF)
|
| 73 |
+
- Drafts and chapters (DOCX, TXT)
|
| 74 |
+
- Notes and outlines
|
| 75 |
+
|
| 76 |
+
## Behind the scenes
|
| 77 |
+
Documents are processed using **RAG (retrieval-augmented generation)**. The system finds the most relevant chunks of your document for each question, so even long documents work well.`,
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
id: 'sessions',
|
| 81 |
+
title: 'Sessions & History',
|
| 82 |
+
icon: 'MessagesSquare',
|
| 83 |
+
content: `# Sessions & History
|
| 84 |
+
|
| 85 |
+
Every conversation is automatically saved as a session.
|
| 86 |
+
|
| 87 |
+
## Finding past chats
|
| 88 |
+
Use the **search bar** in the sidebar to filter your past sessions by title.
|
| 89 |
+
|
| 90 |
+
## Switching between sessions
|
| 91 |
+
Click any session in the sidebar to return to it. Your full context is preserved.
|
| 92 |
+
|
| 93 |
+
## Starting a new chat
|
| 94 |
+
Click the pencil/edit icon next to the search bar to start a fresh conversation.
|
| 95 |
+
|
| 96 |
+
## Renaming or deleting
|
| 97 |
+
Hover any session to reveal the **menu**. From there you can rename or delete.`,
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
id: 'canvas',
|
| 101 |
+
title: 'Progress Canvas',
|
| 102 |
+
icon: 'BarChart3',
|
| 103 |
+
content: `# {{appName}} Canvas
|
| 104 |
+
|
| 105 |
+
The Canvas is a **structured dashboard view** of your PhD journey. It pulls insights from your conversations and organizes them into 10 sections:
|
| 106 |
+
|
| 107 |
+
- Research Progress
|
| 108 |
+
- Methodology
|
| 109 |
+
- Theoretical Framework
|
| 110 |
+
- Challenges & Obstacles
|
| 111 |
+
- Next Steps
|
| 112 |
+
- Writing & Communication
|
| 113 |
+
- Career Development
|
| 114 |
+
- Literature Review
|
| 115 |
+
- Data Analysis
|
| 116 |
+
- Motivation & Mindset
|
| 117 |
+
|
| 118 |
+
## Accessing the Canvas
|
| 119 |
+
Click the **{{appName}} Canvas** button in the sidebar.
|
| 120 |
+
|
| 121 |
+
## Exporting
|
| 122 |
+
You can print or download the Canvas as a snapshot of your progress.`,
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
id: 'tips',
|
| 126 |
+
title: 'Tips & Shortcuts',
|
| 127 |
+
icon: 'Sparkles',
|
| 128 |
+
content: `# Tips & Shortcuts
|
| 129 |
+
|
| 130 |
+
## Get better answers
|
| 131 |
+
- **Provide context.** Mention your field, your stage, your specific concern.
|
| 132 |
+
- **Quote your work.** Paste a paragraph from your draft for targeted feedback.
|
| 133 |
+
- **Use multiple advisors.** Ask one for theory, another for practical next steps.
|
| 134 |
+
|
| 135 |
+
## Useful workflows
|
| 136 |
+
- **Stuck on methodology?** Ask the Methodologist + Theorist together.
|
| 137 |
+
- **Feeling burnt out?** The Motivational Coach + Empathetic Listener help reframe.
|
| 138 |
+
- **Need to be challenged?** Talk to the Constructive Critic and Socratic Mentor.
|
| 139 |
+
|
| 140 |
+
## Theme
|
| 141 |
+
Switch between light and dark mode using the toggle in the top right.`,
|
| 142 |
+
},
|
| 143 |
+
];
|
phd-advisor-frontend/src/pages/ChatPage.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
| 2 |
-
|
|
|
|
|
|
|
| 3 |
import EnhancedChatInput from '../components/EnhancedChatInput';
|
| 4 |
import MessageBubble from '../components/MessageBubble';
|
| 5 |
import ThinkingIndicator from '../components/ThinkingIndicator';
|
|
@@ -824,6 +826,15 @@ const handleNewChat = async (sessionId = null) => {
|
|
| 824 |
|
| 825 |
{/* Theme Toggle */}
|
| 826 |
<ThemeToggle />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
</div>
|
| 828 |
</div>
|
| 829 |
</div>
|
|
|
|
| 1 |
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
| 2 |
+
|
| 3 |
+
import { Home, MessageCircle, Reply, X, Sparkles, Users, Settings2, FileText, Menu, HelpCircle } from 'lucide-react';
|
| 4 |
+
|
| 5 |
import EnhancedChatInput from '../components/EnhancedChatInput';
|
| 6 |
import MessageBubble from '../components/MessageBubble';
|
| 7 |
import ThinkingIndicator from '../components/ThinkingIndicator';
|
|
|
|
| 826 |
|
| 827 |
{/* Theme Toggle */}
|
| 828 |
<ThemeToggle />
|
| 829 |
+
|
| 830 |
+
{/* Help / User Guide */}
|
| 831 |
+
<button
|
| 832 |
+
className="header-help-btn"
|
| 833 |
+
onClick={() => window.dispatchEvent(new CustomEvent('open-user-guide'))}
|
| 834 |
+
title="Open user guide"
|
| 835 |
+
>
|
| 836 |
+
<HelpCircle />
|
| 837 |
+
</button>
|
| 838 |
</div>
|
| 839 |
</div>
|
| 840 |
</div>
|
phd-advisor-frontend/src/styles/ChatPage.css
CHANGED
|
@@ -730,6 +730,32 @@
|
|
| 730 |
background: var(--bg-secondary-dark, #374151);
|
| 731 |
}
|
| 732 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 733 |
/* Loading session indicator */
|
| 734 |
.loading-session {
|
| 735 |
display: flex;
|
|
|
|
| 730 |
background: var(--bg-secondary-dark, #374151);
|
| 731 |
}
|
| 732 |
|
| 733 |
+
.header-help-btn {
|
| 734 |
+
display: flex;
|
| 735 |
+
align-items: center;
|
| 736 |
+
justify-content: center;
|
| 737 |
+
padding: 10px;
|
| 738 |
+
background: var(--bg-primary);
|
| 739 |
+
border: 1px solid var(--border-primary);
|
| 740 |
+
border-radius: 12px;
|
| 741 |
+
cursor: pointer;
|
| 742 |
+
color: var(--text-secondary);
|
| 743 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
| 744 |
+
transition: all 0.2s ease;
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
.header-help-btn svg {
|
| 748 |
+
width: 20px;
|
| 749 |
+
height: 20px;
|
| 750 |
+
display: block;
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
.header-help-btn:hover {
|
| 754 |
+
background: var(--bg-secondary);
|
| 755 |
+
border-color: #2663EB;
|
| 756 |
+
color: #2663EB;
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
/* Loading session indicator */
|
| 760 |
.loading-session {
|
| 761 |
display: flex;
|
phd-advisor-frontend/src/styles/UserGuide.css
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* === User Guide modal ============================================== */
|
| 2 |
+
.ug-overlay {
|
| 3 |
+
position: fixed;
|
| 4 |
+
inset: 0;
|
| 5 |
+
background: rgba(0, 0, 0, 0.6);
|
| 6 |
+
backdrop-filter: blur(4px);
|
| 7 |
+
display: flex;
|
| 8 |
+
align-items: center;
|
| 9 |
+
justify-content: center;
|
| 10 |
+
z-index: 1000;
|
| 11 |
+
animation: ug-fade 0.18s ease;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
@keyframes ug-fade {
|
| 15 |
+
from { opacity: 0; }
|
| 16 |
+
to { opacity: 1; }
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.ug-modal {
|
| 20 |
+
width: 92vw;
|
| 21 |
+
max-width: 1000px;
|
| 22 |
+
height: 80vh;
|
| 23 |
+
max-height: 720px;
|
| 24 |
+
background: var(--bg-primary);
|
| 25 |
+
color: var(--text-primary);
|
| 26 |
+
border: 1px solid var(--border-primary);
|
| 27 |
+
border-radius: 18px;
|
| 28 |
+
box-shadow: 0 30px 80px -10px rgba(0, 0, 0, 0.5);
|
| 29 |
+
display: flex;
|
| 30 |
+
flex-direction: column;
|
| 31 |
+
overflow: hidden;
|
| 32 |
+
animation: ug-pop 0.22s cubic-bezier(0.4, 0, 0.2, 1);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
@keyframes ug-pop {
|
| 36 |
+
from { opacity: 0; transform: scale(0.96); }
|
| 37 |
+
to { opacity: 1; transform: scale(1); }
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/* Header bar */
|
| 41 |
+
.ug-header {
|
| 42 |
+
display: flex;
|
| 43 |
+
align-items: center;
|
| 44 |
+
justify-content: space-between;
|
| 45 |
+
padding: 18px 22px;
|
| 46 |
+
border-bottom: 1px solid var(--border-primary);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.ug-title {
|
| 50 |
+
display: flex;
|
| 51 |
+
align-items: center;
|
| 52 |
+
gap: 10px;
|
| 53 |
+
font-size: 16px;
|
| 54 |
+
font-weight: 700;
|
| 55 |
+
letter-spacing: -0.01em;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.ug-title-icon {
|
| 59 |
+
display: flex;
|
| 60 |
+
align-items: center;
|
| 61 |
+
justify-content: center;
|
| 62 |
+
width: 32px;
|
| 63 |
+
height: 32px;
|
| 64 |
+
border-radius: 8px;
|
| 65 |
+
background: #2663EB;
|
| 66 |
+
color: #fff;
|
| 67 |
+
flex-shrink: 0;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.ug-close {
|
| 71 |
+
display: flex;
|
| 72 |
+
align-items: center;
|
| 73 |
+
justify-content: center;
|
| 74 |
+
width: 32px;
|
| 75 |
+
height: 32px;
|
| 76 |
+
border: none;
|
| 77 |
+
background: transparent;
|
| 78 |
+
border-radius: 8px;
|
| 79 |
+
cursor: pointer;
|
| 80 |
+
color: var(--text-secondary);
|
| 81 |
+
transition: background 0.15s ease, color 0.15s ease;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.ug-close:hover {
|
| 85 |
+
background: var(--bg-secondary);
|
| 86 |
+
color: var(--text-primary);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* Body layout: sidebar + content */
|
| 90 |
+
.ug-body {
|
| 91 |
+
flex: 1;
|
| 92 |
+
display: grid;
|
| 93 |
+
grid-template-columns: 240px 1fr;
|
| 94 |
+
min-height: 0;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/* Sidebar */
|
| 98 |
+
.ug-sidebar {
|
| 99 |
+
display: flex;
|
| 100 |
+
flex-direction: column;
|
| 101 |
+
border-right: 1px solid var(--border-primary);
|
| 102 |
+
background: var(--bg-secondary);
|
| 103 |
+
min-height: 0;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.ug-search {
|
| 107 |
+
display: flex;
|
| 108 |
+
align-items: center;
|
| 109 |
+
gap: 8px;
|
| 110 |
+
margin: 14px;
|
| 111 |
+
padding: 8px 12px;
|
| 112 |
+
background: var(--bg-primary);
|
| 113 |
+
border: 1px solid var(--border-primary);
|
| 114 |
+
border-radius: 10px;
|
| 115 |
+
color: var(--text-secondary);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.ug-search input {
|
| 119 |
+
flex: 1;
|
| 120 |
+
border: none;
|
| 121 |
+
outline: none;
|
| 122 |
+
background: transparent;
|
| 123 |
+
color: var(--text-primary);
|
| 124 |
+
font-size: 13.5px;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.ug-search input::placeholder {
|
| 128 |
+
color: var(--text-tertiary);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.ug-toc {
|
| 132 |
+
flex: 1;
|
| 133 |
+
overflow-y: auto;
|
| 134 |
+
padding: 4px 10px 14px;
|
| 135 |
+
display: flex;
|
| 136 |
+
flex-direction: column;
|
| 137 |
+
gap: 2px;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.ug-toc-item {
|
| 141 |
+
display: flex;
|
| 142 |
+
align-items: center;
|
| 143 |
+
gap: 10px;
|
| 144 |
+
padding: 10px 12px;
|
| 145 |
+
border: none;
|
| 146 |
+
background: transparent;
|
| 147 |
+
border-radius: 8px;
|
| 148 |
+
cursor: pointer;
|
| 149 |
+
color: var(--text-secondary);
|
| 150 |
+
font-size: 13.5px;
|
| 151 |
+
font-weight: 500;
|
| 152 |
+
text-align: left;
|
| 153 |
+
transition: background 0.15s ease, color 0.15s ease;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.ug-toc-item span {
|
| 157 |
+
flex: 1;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.ug-toc-arrow {
|
| 161 |
+
opacity: 0;
|
| 162 |
+
transition: opacity 0.15s ease, transform 0.15s ease;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.ug-toc-item:hover {
|
| 166 |
+
background: var(--bg-primary);
|
| 167 |
+
color: var(--text-primary);
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.ug-toc-item:hover .ug-toc-arrow {
|
| 171 |
+
opacity: 0.6;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.ug-toc-item.active {
|
| 175 |
+
background: rgba(38, 99, 235, 0.1);
|
| 176 |
+
color: #2663EB;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.ug-toc-item.active .ug-toc-arrow {
|
| 180 |
+
opacity: 1;
|
| 181 |
+
transform: translateX(2px);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.ug-empty {
|
| 185 |
+
padding: 14px;
|
| 186 |
+
font-size: 13px;
|
| 187 |
+
color: var(--text-tertiary);
|
| 188 |
+
text-align: center;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/* Content area */
|
| 192 |
+
.ug-content {
|
| 193 |
+
overflow-y: auto;
|
| 194 |
+
padding: 28px 36px 40px;
|
| 195 |
+
font-size: 15px;
|
| 196 |
+
line-height: 1.65;
|
| 197 |
+
color: var(--text-primary);
|
| 198 |
+
animation: ug-slide 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
@keyframes ug-slide {
|
| 202 |
+
from { opacity: 0; transform: translateY(6px); }
|
| 203 |
+
to { opacity: 1; transform: translateY(0); }
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.ug-content h1 {
|
| 207 |
+
margin: 0 0 16px;
|
| 208 |
+
font-size: 26px;
|
| 209 |
+
font-weight: 800;
|
| 210 |
+
letter-spacing: -0.02em;
|
| 211 |
+
color: var(--text-primary);
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.ug-content h2 {
|
| 215 |
+
margin: 28px 0 10px;
|
| 216 |
+
font-size: 18px;
|
| 217 |
+
font-weight: 700;
|
| 218 |
+
color: var(--text-primary);
|
| 219 |
+
letter-spacing: -0.01em;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.ug-content h3 {
|
| 223 |
+
margin: 22px 0 6px;
|
| 224 |
+
font-size: 15px;
|
| 225 |
+
font-weight: 700;
|
| 226 |
+
color: var(--text-primary);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.ug-content p {
|
| 230 |
+
margin: 0 0 14px;
|
| 231 |
+
color: var(--text-secondary);
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.ug-content ul, .ug-content ol {
|
| 235 |
+
margin: 0 0 16px;
|
| 236 |
+
padding-left: 22px;
|
| 237 |
+
color: var(--text-secondary);
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.ug-content li {
|
| 241 |
+
margin-bottom: 6px;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.ug-content strong {
|
| 245 |
+
color: var(--text-primary);
|
| 246 |
+
font-weight: 600;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.ug-content code {
|
| 250 |
+
padding: 1px 6px;
|
| 251 |
+
border-radius: 4px;
|
| 252 |
+
background: var(--bg-secondary);
|
| 253 |
+
font-size: 0.9em;
|
| 254 |
+
color: #2663EB;
|
| 255 |
+
font-family: 'SF Mono', ui-monospace, monospace;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
.ug-content a {
|
| 259 |
+
color: #2663EB;
|
| 260 |
+
text-decoration: none;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.ug-content a:hover {
|
| 264 |
+
text-decoration: underline;
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
/* Mobile */
|
| 268 |
+
@media (max-width: 720px) {
|
| 269 |
+
.ug-body {
|
| 270 |
+
grid-template-columns: 1fr;
|
| 271 |
+
grid-template-rows: auto 1fr;
|
| 272 |
+
}
|
| 273 |
+
.ug-sidebar {
|
| 274 |
+
border-right: none;
|
| 275 |
+
border-bottom: 1px solid var(--border-primary);
|
| 276 |
+
max-height: 200px;
|
| 277 |
+
}
|
| 278 |
+
.ug-content {
|
| 279 |
+
padding: 22px 22px 36px;
|
| 280 |
+
}
|
| 281 |
+
}
|