Explorar o código

feat: 优化UI界面与样式,新增帮助中心、页脚及联系客服交流群功能

bob hai 2 semanas
pai
achega
ec39a2e209

BIN=BIN
public/contact-group-qrcode.jpg


+ 0 - 1
server/templates.ts

@@ -8,7 +8,6 @@ export const COURSE_TEMPLATES: CourseTemplate[] = [
     id: 'party-building-standard',
     name: '《坚持人民至上根本立场》党课视频',
     category: '党政专题',
-    audience: '党员干部 / 青年理论学习小组',
     tags: ['精品党课', 'AI数字人', '1080P高清', 'PPT同步论证'],
     badge: '官方推荐示范',
     summary: '严格依据党课制作任务书规范,包含审定讲稿、全屏课件切换逻辑、数字人讲师近景与全景运镜、精准语速停顿控制。',

+ 2 - 0
src/App.tsx

@@ -5,6 +5,7 @@ import { NewCourse } from './pages/NewCourse'
 import { CourseWorkspace } from './pages/CourseWorkspace'
 import { AuthPage } from './pages/AuthPage'
 import { Profile } from './pages/Profile'
+import { HelpPage } from './pages/HelpPage'
 import { AdminLayout } from './pages/admin/AdminLayout'
 import { AdminDashboard } from './pages/admin/AdminDashboard'
 import { AdminCourses } from './pages/admin/AdminCourses'
@@ -38,6 +39,7 @@ export default function App() {
           <Route path="courses/new" element={<NewCourse />} />
           <Route path="courses/:id" element={<CourseWorkspace />} />
           <Route path="profile" element={<Profile />} />
+          <Route path="help" element={<HelpPage />} />
         </Route>
       ) : null}
 

BIN=BIN
src/assets/contact-group-qrcode.jpg


+ 36 - 4
src/components/Brand.tsx

@@ -1,6 +1,38 @@
 export function Brand({ compact = false }: { compact?: boolean }) {
-  return <div className={`brand ${compact ? 'brand--compact' : ''}`} aria-label="星痕智能课程工坊">
-    <span className="brand-mark" aria-hidden="true"><i /><b /></span>
-    <span><strong>星痕</strong>{!compact && <small>XINGHEN</small>}</span>
-  </div>
+  return (
+    <div className={`brand ${compact ? 'brand--compact' : ''}`} aria-label="星痕智能课程工坊">
+      <span className="brand-mark" aria-hidden="true">
+        <svg
+          className="brand-mark-svg"
+          viewBox="0 0 44 26"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <defs>
+            <linearGradient id="brandGoldDot" x1="0%" y1="0%" x2="100%" y2="100%">
+              <stop offset="0%" stopColor="#deb66c" />
+              <stop offset="100%" stopColor="#b58735" />
+            </linearGradient>
+            <linearGradient id="brandArcTrail" x1="0%" y1="0%" x2="100%" y2="0%">
+              <stop offset="0%" stopColor="#8292a8" />
+              <stop offset="60%" stopColor="#8fa0b5" />
+              <stop offset="100%" stopColor="#a5b5cb" />
+            </linearGradient>
+          </defs>
+          <circle cx="8" cy="18" r="3.6" fill="url(#brandGoldDot)" />
+          <path
+            d="M 14.2 14.2 C 20.2 7.2, 30.2 4.6, 41 10"
+            stroke="url(#brandArcTrail)"
+            strokeWidth="1.8"
+            strokeLinecap="round"
+          />
+        </svg>
+      </span>
+      <span className="brand-text">
+        <strong className="brand-title">星痕</strong>
+        {!compact && <small className="brand-sub">XINGHEN</small>}
+      </span>
+    </div>
+  )
 }
+

+ 98 - 0
src/components/ContactModal.tsx

@@ -0,0 +1,98 @@
+import { MessageSquare, QrCode, Sparkles, X } from 'lucide-react'
+import contactQrCode from '../assets/contact-group-qrcode.jpg'
+
+interface ContactModalProps {
+  onClose: () => void
+}
+
+export function ContactModal({ onClose }: ContactModalProps) {
+  return (
+    <div className="modal-backdrop" onClick={onClose}>
+      <div
+        className="modal-dialog contact-modal"
+        onClick={(e) => e.stopPropagation()}
+      >
+        <div className="modal-header">
+          <div>
+            <span className="modal-badge highlight-badge">CONTACT SUPPORT</span>
+            <h3>联系星痕 · 专属制作顾问</h3>
+          </div>
+          <button
+            type="button"
+            className="close-modal-btn"
+            onClick={onClose}
+            title="关闭"
+          >
+            <X size={18} />
+          </button>
+        </div>
+
+        <div className="modal-body contact-modal-body">
+          <div className="contact-modal-intro">
+            <p>
+              请扫描下方二维码添加微信,加入<strong>「星痕课程制作沟通群」</strong>。
+              制作团队将在群内为您同步排期制作进度,并提供一对一课件制作支持。
+            </p>
+          </div>
+
+          <div className="contact-modal-qr-section">
+            <div className="contact-modal-qr-card">
+              <div className="qr-image-wrapper">
+                <img
+                  src={contactQrCode}
+                  alt="微信群二维码"
+                  className="contact-modal-qr-img"
+                />
+              </div>
+              <div className="qr-badge-text">
+                <QrCode size={14} />
+                <span>微信扫一扫 · 加群联系</span>
+              </div>
+            </div>
+
+            <div className="contact-modal-steps">
+              <div className="contact-step-card">
+                <span className="step-badge-num">1</span>
+                <div className="step-content">
+                  <strong>扫码进群</strong>
+                  <p>微信扫描左侧二维码加入专属制作对接群</p>
+                </div>
+              </div>
+
+              <div className="contact-step-card">
+                <span className="step-badge-num">2</span>
+                <div className="step-content">
+                  <strong>核对课程信息</strong>
+                  <p>群内发送您提交的课程名称,以便顾问快速定位</p>
+                </div>
+              </div>
+
+              <div className="contact-step-card">
+                <span className="step-badge-num">3</span>
+                <div className="step-content">
+                  <strong>确认排期与交付</strong>
+                  <p>实时沟通制作方案与细节,跟进分集交付状态</p>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div className="contact-modal-footer-tip">
+            <Sparkles size={16} />
+            <span>如二维码过期或无法进群,可联系平台客服为您手动拉群。</span>
+          </div>
+        </div>
+
+        <div className="modal-footer">
+          <button
+            type="button"
+            className="primary-button full-width"
+            onClick={onClose}
+          >
+            我知道了
+          </button>
+        </div>
+      </div>
+    </div>
+  )
+}

+ 3 - 9
src/components/DeleteCourseModal.tsx

@@ -71,15 +71,9 @@ export function DeleteCourseModal({
       }}
     >
       <div className="modal-dialog delete-course-modal" role="dialog" aria-modal="true">
-        <div className="modal-header danger-header">
-          <div className="danger-header-title">
-            <div className="danger-icon-badge">
-              <AlertTriangle size={20} />
-            </div>
-            <div>
-              <span className="modal-badge danger-badge">DANGER ZONE</span>
-              <h3>删除课程</h3>
-            </div>
+        <div className="modal-header">
+          <div className="modal-header-simple-title">
+            <h3>删除课程</h3>
           </div>
           <button
             type="button"

+ 30 - 0
src/components/Footer.tsx

@@ -0,0 +1,30 @@
+import { Link } from 'react-router-dom'
+
+export function Footer() {
+  return (
+    <footer className="site-footer">
+      <div className="site-footer-inner">
+        <div className="site-footer-company">
+          <span>杭州纯粹星辰人工智能科技有限公司</span>
+          <span className="site-footer-dot">·</span>
+          <span>2026 版权所有</span>
+        </div>
+
+        <div className="site-footer-links">
+          <a
+            href="https://beian.miit.gov.cn/"
+            target="_blank"
+            rel="noreferrer"
+            className="site-footer-beian"
+          >
+            浙ICP备2026057502号
+          </a>
+          <span className="site-footer-sep">·</span>
+          <Link to="/help" className="site-footer-help-link">
+            帮助中心
+          </Link>
+        </div>
+      </div>
+    </footer>
+  )
+}

+ 5 - 3
src/components/Layout.tsx

@@ -1,6 +1,7 @@
 import { CircleHelp, LogOut, UserRound } from 'lucide-react'
 import { NavLink, Outlet } from 'react-router-dom'
 import { Brand } from './Brand'
+import { Footer } from './Footer'
 import { useAuth } from '../AuthContext'
 
 export function Layout() {
@@ -22,17 +23,18 @@ export function Layout() {
             <UserRound size={16} />
             <span>{user?.contactName || user?.phone}</span>
           </NavLink>
-          <button className="icon-button" title="帮助">
+          <NavLink to="/help" className="icon-button" title="使用帮助">
             <CircleHelp size={19} />
-          </button>
+          </NavLink>
           <button className="icon-button" title="退出登录" onClick={logout}>
             <LogOut size={18} />
           </button>
         </div>
       </header>
-      <main>
+      <main className="app-main">
         <Outlet />
       </main>
+      <Footer />
     </div>
   )
 }

+ 95 - 2
src/pages/AuthPage.tsx

@@ -1,2 +1,95 @@
-import{FormEvent,useState}from'react';import{Navigate}from'react-router-dom';import{ArrowRight,LockKeyhole,Phone}from'lucide-react';import{Brand}from'../components/Brand';import{useAuth}from'../AuthContext'
-export function AuthPage(){const{user,authenticate}=useAuth(),[mode,setMode]=useState<'login'|'register'>('login'),[busy,setBusy]=useState(false),[error,setError]=useState('');if(user)return <Navigate to="/" replace/>;async function submit(e:FormEvent<HTMLFormElement>){e.preventDefault();const f=new FormData(e.currentTarget);setBusy(true);setError('');try{await authenticate(mode,String(f.get('phone')),String(f.get('password')))}catch(x){setError(x instanceof Error?x.message:'操作失败')}finally{setBusy(false)}}return <div className="auth-page"><section className="auth-brand"><Brand/><div><p className="eyebrow">智能课程工坊</p><h1>让每一门好课,<br/><em>留下清晰的星痕。</em></h1><p>归集课程资产与讲师信息,让课程制作更简单。</p></div></section><form className="auth-card" onSubmit={submit}><span className="section-kicker">WELCOME TO XINGHEN</span><h2>{mode==='login'?'登录星痕':'创建账号'}</h2><p>{mode==='login'?'使用手机号和密码继续':'注册后即可开始'}</p><label>手机号<div className="input-with-icon"><Phone/><input name="phone" inputMode="numeric" required pattern="1[0-9]{10}" placeholder="请输入 11 位手机号"/></div></label><label>密码<div className="input-with-icon"><LockKeyhole/><input name="password" type="password" required minLength={6} placeholder="至少 6 位密码"/></div></label>{error&&<div className="notice notice-error">{error}</div>}<button className="primary-button full-button" disabled={busy}>{busy?'请稍候…':mode==='login'?'登录':'注册'}<ArrowRight size={17}/></button><button type="button" className="auth-switch" onClick={()=>{setMode(mode==='login'?'register':'login');setError('')}}>{mode==='login'?'还没有账号?立即注册':'已有账号?返回登录'}</button></form></div>}
+import { FormEvent, useState } from 'react'
+import { Navigate } from 'react-router-dom'
+import { ArrowRight, LockKeyhole, Phone } from 'lucide-react'
+import { Brand } from '../components/Brand'
+import { Footer } from '../components/Footer'
+import { useAuth } from '../AuthContext'
+
+export function AuthPage() {
+  const { user, authenticate } = useAuth()
+  const [mode, setMode] = useState<'login' | 'register'>('login')
+  const [busy, setBusy] = useState(false)
+  const [error, setError] = useState('')
+
+  if (user) return <Navigate to="/" replace />
+
+  async function submit(e: FormEvent<HTMLFormElement>) {
+    e.preventDefault()
+    const f = new FormData(e.currentTarget)
+    setBusy(true)
+    setError('')
+    try {
+      await authenticate(mode, String(f.get('phone')), String(f.get('password')))
+    } catch (x) {
+      setError(x instanceof Error ? x.message : '操作失败')
+    } finally {
+      setBusy(false)
+    }
+  }
+
+  return (
+    <div className="auth-wrapper">
+      <div className="auth-page">
+        <section className="auth-brand">
+          <Brand />
+          <div>
+            <p className="">智能课程工坊</p>
+            <h1>
+              让每一门好课,
+              <br />
+              <em>留下清晰的星痕。</em>
+            </h1>
+            <p>归集课程资产与讲师信息,让课程制作更简单。</p>
+          </div>
+        </section>
+        <form className="auth-card" onSubmit={submit}>
+          <span className="section-kicker">WELCOME TO XINGHEN</span>
+          <h2>{mode === 'login' ? '登录星痕' : '创建账号'}</h2>
+          <p>{mode === 'login' ? '使用手机号和密码继续' : '注册后即可开始'}</p>
+          <label>
+            手机号
+            <div className="input-with-icon">
+              <Phone />
+              <input
+                name="phone"
+                inputMode="numeric"
+                required
+                pattern="1[0-9]{10}"
+                placeholder="请输入 11 位手机号"
+              />
+            </div>
+          </label>
+          <label>
+            密码
+            <div className="input-with-icon">
+              <LockKeyhole />
+              <input
+                name="password"
+                type="password"
+                required
+                minLength={6}
+                placeholder="至少 6 位密码"
+              />
+            </div>
+          </label>
+          {error && <div className="notice notice-error">{error}</div>}
+          <button className="primary-button full-button" disabled={busy}>
+            {busy ? '请稍候…' : mode === 'login' ? '登录' : '注册'}
+            <ArrowRight size={17} />
+          </button>
+          <button
+            type="button"
+            className="auth-switch"
+            onClick={() => {
+              setMode(mode === 'login' ? 'register' : 'login')
+              setError('')
+            }}
+          >
+            {mode === 'login' ? '还没有账号?立即注册' : '已有账号?返回登录'}
+          </button>
+        </form>
+      </div>
+      <Footer />
+    </div>
+  )
+}

+ 121 - 37
src/pages/CourseWorkspace.tsx

@@ -11,6 +11,8 @@ import {
   FileText,
   Layers,
   LoaderCircle,
+  MessageCircle,
+  MessageSquare,
   PackageCheck,
   Plus,
   Save,
@@ -28,6 +30,8 @@ import { StatusBadge } from '../components/StatusBadge'
 import { AddInstructorModal } from '../components/AddInstructorModal'
 import { EpisodeModal } from '../components/EpisodeModal'
 import { DeleteCourseModal } from '../components/DeleteCourseModal'
+import { ContactModal } from '../components/ContactModal'
+import contactQrCode from '../assets/contact-group-qrcode.jpg'
 import type { Course, Episode } from '../types'
 
 const size = (n: number) =>
@@ -47,6 +51,7 @@ export function CourseWorkspace() {
   const [showAddInstructorModal, setShowAddInstructorModal] = useState(false)
   const [showEpisodeModal, setShowEpisodeModal] = useState(false)
   const [showDeleteModal, setShowDeleteModal] = useState(false)
+  const [showContactModal, setShowContactModal] = useState(false)
   const [editingEpisode, setEditingEpisode] = useState<Episode | null>(null)
 
   // 全局资产待上传
@@ -352,9 +357,11 @@ export function CourseWorkspace() {
             </span>
           </div>
 
-          <Link className="secondary-button" to="/">
-            返回课程列表
-          </Link>
+          <div className="success-panel-actions">
+            <Link className="secondary-button" to="/">
+              返回课程列表
+            </Link>
+          </div>
         </section>
       )}
 
@@ -387,9 +394,19 @@ export function CourseWorkspace() {
               <strong>{course.submittedAt ? new Date(course.submittedAt).toLocaleString('zh-CN') : '—'}</strong>
             </span>
           </div>
-          <Link className="secondary-button" to="/">
-            返回课程列表
-          </Link>
+          <div className="success-panel-actions">
+            <Link className="secondary-button" to="/">
+              返回课程列表
+            </Link>
+            <button
+              type="button"
+              className="secondary-button"
+              onClick={() => setShowContactModal(true)}
+            >
+              <MessageCircle size={15} />
+              联系星痕
+            </button>
+          </div>
         </section>
       )}
 
@@ -418,9 +435,62 @@ export function CourseWorkspace() {
               <strong>{course.submittedAt ? new Date(course.submittedAt).toLocaleString('zh-CN') : '—'}</strong>
             </span>
           </div>
-          <Link className="secondary-button" to="/">
-            返回课程列表
-          </Link>
+
+          {/* 联系星痕 · 进群引导与二维码卡片 */}
+          <div className="contact-support-card">
+            <div className="contact-card-header">
+              <div className="contact-icon-badge">
+                <MessageSquare size={18} />
+              </div>
+              <div className="contact-header-text">
+                <h4>联系星痕 · 添加微信进群跟进排期</h4>
+                <p>建议添加微信并加入官方制作沟通群,与专属制作顾问对接排期进度与个性化需求</p>
+              </div>
+            </div>
+
+            <div className="contact-card-body">
+              <div className="contact-qr-container">
+                <div className="contact-qr-box">
+                  <img
+                    src={contactQrCode}
+                    alt="联系星痕 - 微信群二维码"
+                    className="contact-qr-image"
+                  />
+                </div>
+                <span className="contact-qr-caption">扫描二维码加入群聊</span>
+              </div>
+
+              <div className="contact-card-steps">
+                <div className="contact-step-row">
+                  <span className="step-circle">1</span>
+                  <div className="step-desc">
+                    <strong>微信扫码进群</strong>
+                    <span>使用微信扫描二维码,加入「星痕课程制作沟通群」</span>
+                  </div>
+                </div>
+                <div className="contact-step-row">
+                  <span className="step-circle">2</span>
+                  <div className="step-desc">
+                    <strong>发送课程名称</strong>
+                    <span>进群后发送已提交课程的名称,以便专属顾问快速核对并建立制作档案</span>
+                  </div>
+                </div>
+                <div className="contact-step-row">
+                  <span className="step-circle">3</span>
+                  <div className="step-desc">
+                    <strong>确认排期与跟进交付</strong>
+                    <span>制作团队将与您一对一沟通细节,并在制作完成后第一时间在此处交付</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div className="success-panel-actions">
+            <Link className="secondary-button" to="/">
+              返回课程列表
+            </Link>
+          </div>
         </section>
       )}
 
@@ -537,7 +607,7 @@ export function CourseWorkspace() {
                             讲稿 ({ep.lectureNotes.length}字)
                           </span>
                         ) : (
-                          <span className="tag-pill tag-pill-dim">待补充讲稿</span>
+                          <span className="tag-pill tag-pill-dim">未添加讲稿</span>
                         )}
 
                         <span className="tag-pill tag-pill-info">
@@ -574,13 +644,21 @@ export function CourseWorkspace() {
                       <div className="episode-card-body">
                         {ep.summary && <p className="ep-summary-text"><strong>分集要点:</strong>{ep.summary}</p>}
 
+                        <div className="episode-content-heading">
+                          <div>
+                            <strong>本集制作内容</strong>
+                            <span>口播稿与素材均为选填,可按实际制作方式添加</span>
+                          </div>
+                        </div>
+
                         <div className="ep-body-grid">
-                          {/* 左半:口播讲稿正文 */}
+                          {/* 口播稿(选填) */}
                           <div className="ep-notes-box">
                             <div className="box-title-row">
                               <div className="title-left">
                                 <FileText size={15} />
-                                <strong>本集口播讲稿正文</strong>
+                                <strong>口播稿</strong>
+                                <span className="optional-label">选填</span>
                               </div>
                               {isNotesDirty && <span className="dirty-badge">未保存</span>}
                             </div>
@@ -592,7 +670,7 @@ export function CourseWorkspace() {
                                 const val = e.target.value
                                 setNotesDraftMap((prev) => ({ ...prev, [ep.id]: val }))
                               }}
-                              placeholder="直接在此输入或粘贴本集的口播讲稿/字幕文稿(如第一讲引入、推导、例题等)…"
+                              placeholder="可直接输入或粘贴本集的口播稿、字幕文稿…"
                             />
                             <div className="notes-footer-row">
                               <small>共 {draftNotes.length} 字</small>
@@ -608,13 +686,28 @@ export function CourseWorkspace() {
                             </div>
                           </div>
 
-                          {/* 右半:本集专属资产上传与管理 */}
+                          {/* 本集素材(选填) */}
                           <div className="ep-assets-box">
                             <div className="box-title-row">
                               <div className="title-left">
                                 <FileSpreadsheet size={15} />
-                                <strong>本集专属素材 ({epAssets.length})</strong>
+                                <strong>上传素材</strong>
+                                <span className="optional-label">选填</span>
+                                {epAssets.length > 0 && <span className="asset-count-label">{epAssets.length} 份</span>}
                               </div>
+                              <label className="ep-file-label compact-upload-button">
+                                <Upload size={14} />
+                                <span>选择文件</span>
+                                <input
+                                  type="file"
+                                  multiple
+                                  hidden
+                                  onChange={(e) => {
+                                    const selected = Array.from(e.target.files || [])
+                                    setEpFilesMap((prev) => ({ ...prev, [ep.id]: selected }))
+                                  }}
+                                />
+                              </label>
                             </div>
 
                             {/* 本集已有素材列表 */}
@@ -637,26 +730,12 @@ export function CourseWorkspace() {
                                 </div>
                               ))}
                               {!epAssets.length && (
-                                <p className="empty-sub-tip">本集暂无独立附件文件,可上传课件 PPT、Word 讲稿等。</p>
+                                <p className="empty-sub-tip">如有需要,可上传本集使用的 PPT、Word、图片等制作素材。</p>
                               )}
                             </div>
 
-                            {/* 本集文件选择区 */}
+                            {/* 待上传文件 */}
                             <div className="ep-upload-zone">
-                              <label className="ep-file-label">
-                                <Upload size={14} />
-                                <span>选择本集课件/讲稿附件</span>
-                                <input
-                                  type="file"
-                                  multiple
-                                  hidden
-                                  onChange={(e) => {
-                                    const selected = Array.from(e.target.files || [])
-                                    setEpFilesMap((prev) => ({ ...prev, [ep.id]: selected }))
-                                  }}
-                                />
-                              </label>
-
                               {pendingFiles.length > 0 && (
                                 <div className="ep-pending-files">
                                   <div className="pending-chips">
@@ -824,12 +903,12 @@ export function CourseWorkspace() {
         </div>
       )}
 
-      {/* 课程详情最底部:危险操作区 / 删除课程 */}
+      {/* 课程详情最底部:低调的辅助操作区 / 删除课程 */}
       <section className="course-danger-section">
         <div className="danger-zone-card">
           <div className="danger-zone-info">
             <div className="danger-zone-title-row">
-              <Trash2 size={18} className="danger-icon" />
+              <Trash2 size={15} className="danger-icon" />
               <h4>删除课程</h4>
             </div>
             <p>
@@ -842,20 +921,20 @@ export function CourseWorkspace() {
             {isDraft ? (
               <button
                 type="button"
-                className="danger-button"
+                className="danger-button-subtle"
                 onClick={() => setShowDeleteModal(true)}
               >
-                <Trash2 size={15} />
+                <Trash2 size={14} />
                 删除课程
               </button>
             ) : (
               <button
                 type="button"
-                className="danger-button disabled"
+                className="danger-button-subtle disabled"
                 disabled
                 title="已等待制作或制作完成的课程不能删除"
               >
-                <Trash2 size={15} />
+                <Trash2 size={14} />
                 不可删除
               </button>
             )}
@@ -898,6 +977,11 @@ export function CourseWorkspace() {
         }}
       />
 
+      {/* 联系星痕沟通弹窗 */}
+      {showContactModal && (
+        <ContactModal onClose={() => setShowContactModal(false)} />
+      )}
+
       {error && <div className="notice notice-error">{error}</div>}
     </div>
   )

+ 392 - 0
src/pages/HelpPage.tsx

@@ -0,0 +1,392 @@
+import { useState } from 'react'
+import {
+  ArrowLeft,
+  ChevronDown,
+  ChevronUp,
+  FileQuestion,
+  Layers,
+  MessageCircle,
+  MessageSquare,
+  Search,
+  Sparkles,
+  UploadCloud,
+  UserCheck,
+  Video,
+} from 'lucide-react'
+import { Link } from 'react-router-dom'
+import { ContactModal } from '../components/ContactModal'
+
+interface FAQItem {
+  id: string
+  category: 'getting-started' | 'materials' | 'production' | 'account'
+  question: string
+  answer: string | React.ReactNode
+}
+
+const CATEGORIES = [
+  { key: 'all', label: '全部问题' },
+  { key: 'getting-started', label: '入门与创建' },
+  { key: 'materials', label: '课件与素材规范' },
+  { key: 'production', label: '制作与交付' },
+  { key: 'account', label: '服务与支持' },
+] as const
+
+const FAQ_LIST: FAQItem[] = [
+  {
+    id: 'what-is-xinghen',
+    category: 'getting-started',
+    question: '星痕·智能课程工坊是什么?它能为我做什么?',
+    answer:
+      '星痕是一个专为知识创作者、讲师和教研团队打造的数字化课程生产工坊。通过标准化的分集大纲编排、讲师形象资产归集与课件整理,星痕能将您的教案素材高效转化为高质量的音视频课件与数字化成品。',
+  },
+  {
+    id: 'how-to-start',
+    category: 'getting-started',
+    question: '制作一门新课程的标准流程是怎样的?',
+    answer: (
+      <div>
+        <p style={{ margin: '0 0 8px' }}>制作一门完整的课程通常包含以下 4 个步骤:</p>
+        <ol style={{ margin: 0, paddingLeft: '20px', lineHeight: 1.8 }}>
+          <li><strong>创建课程</strong>:填写课程主题、分类、目标受众与简介,也可直接从官方模板一键生成;</li>
+          <li><strong>编排分集大纲</strong>:添加各个分集的章节名称、预计时长与口播逐字稿;</li>
+          <li><strong>配置讲师与上传素材</strong>:上传讲师出镜形象照片(坐姿/站姿)及配套 PPT 课件;</li>
+          <li><strong>提交制作</strong>:确认材料无误后点击“提交制作”,制作团队将跟进合成并交付最终视频。</li>
+        </ol>
+      </div>
+    ),
+  },
+  {
+    id: 'course-templates',
+    category: 'getting-started',
+    question: '新手可以如何快速体验?有预设模板吗?',
+    answer:
+      '在创建课程页面,我们提供了多种精选的官方课程模板(如企业培训、通识科普、专业讲座等)。点击“使用示例”即可一键预填完整的分集大纲、讲师配置与配套素材,方便您快速熟悉全套工作流。',
+  },
+  {
+    id: 'ppt-requirements',
+    category: 'materials',
+    question: '上传 PPT / 课件文件有什么格式和要求?',
+    answer:
+      '支持常见的 PPT、PPTX、PDF 以及压缩包文件。建议课件页面比例为 16:9(1920×1080 最佳),文字排版留有合适边距;若包含特殊字体或复杂排版动画,建议在提交前同步导出或上传一份 PDF 副本以便制作核对。',
+  },
+  {
+    id: 'instructor-photo-guidelines',
+    category: 'materials',
+    question: '讲师照片与出镜形象有什么拍摄或上传规范?',
+    answer: (
+      <div>
+        <p style={{ margin: '0 0 6px' }}>为保证最终合成的画质与真实感,推荐遵守以下要求:</p>
+        <ul style={{ margin: 0, paddingLeft: '18px', lineHeight: 1.7 }}>
+          <li><strong>背景要求</strong>:优先使用纯色背景(白底/绿幕/纯色墙面)或已抠图的透明背景 PNG;</li>
+          <li><strong>姿态清晰</strong>:支持上传站姿、坐姿等高清照片,面部与服饰光线充足均匀,避免逆光与重度阴影;</li>
+          <li><strong>格式大小</strong>:支持 JPG、PNG、WebP 等格式,单张建议在 5MB 以内。</li>
+        </ul>
+      </div>
+    ),
+  },
+  {
+    id: 'script-writing-tips',
+    category: 'materials',
+    question: '分集逐字稿(口播文案)怎么写更利于制作?',
+    answer:
+      '建议逐字稿使用通俗易懂的口语化表达,按讲解节奏合理分段并添加标点符号;对于生僻字、英文缩写或专业术语,可标注拼音或中文读音,以确保声音合成与讲解语调准确自然。',
+  },
+  {
+    id: 'production-timeline',
+    category: 'production',
+    question: '课程提交制作后,通常需要多长时间交付?',
+    answer:
+      '提交制作后,课程会进入“等待制作”队列。通常 1~3 集的轻量课程在 1~3 个工作日内完成初版合成与质检;多集复杂课程将根据总时长与排期协商交付时间。您可以在控制台随时关注最新进度。',
+  },
+  {
+    id: 'course-statuses-meaning',
+    category: 'production',
+    question: '课程的不同状态分别代表什么意思?',
+    answer: (
+      <div>
+        <p style={{ margin: '0 0 6px' }}>课程生命周期包含以下状态:</p>
+        <ul style={{ margin: 0, paddingLeft: '18px', lineHeight: 1.7 }}>
+          <li><span className="status status--draft">草稿 DRAFT</span>:正在编辑完善阶段,可随时增删分集与素材;</li>
+          <li><span className="status status--waiting_production">等待制作 WAITING</span>:已确认并提交,等待制作团队认领排期;</li>
+          <li><span className="status status--in_production">制作中 IN PRODUCTION</span>:团队正在进行音视频渲染、剪辑与合成;</li>
+          <li><span className="status status--completed">已完成 COMPLETED</span>:制作完成,所有分集交付物已上架供下载。</li>
+        </ul>
+      </div>
+    ),
+  },
+  {
+    id: 'edit-after-submit',
+    category: 'production',
+    question: '提交制作后如果发现材料有误,还能修改吗?',
+    answer:
+      '课程提交后工作区将进入锁定保护状态,以保证制作期间版本的一致性。如需紧急更新脚本或替换课件,可直接点击页面底部的“联系制作顾问”加入微信沟通群,告知客服进行素材补充或退回调整。',
+  },
+  {
+    id: 'deliverables-download',
+    category: 'production',
+    question: '制作完成后我如何查看和下载最终成品?',
+    answer:
+      '课程状态变更为“已完成”后,进入该课程工作区即可看到绿色的「已交付成果」面板。您可以直接在线播放预览成片,或一键下载 MP4 高清视频文件及配套课件成果。',
+  },
+  {
+    id: 'how-to-contact',
+    category: 'account',
+    question: '遇到问题如何联系官方制作顾问与技术支持?',
+    answer:
+      '您可以点击页面上的“联系顾问”按钮,扫描弹窗中的二维码加入「星痕课程制作沟通群」,将有专属制作顾问为您提供一对一咨询、进度追踪与课件定制支持。',
+  },
+  {
+    id: 'profile-and-security',
+    category: 'account',
+    question: '如何修改我的联系人姓名、单位或登录密码?',
+    answer:
+      '点击顶部导航栏右上角的个人账号名称(或前往「个人中心 /profile」),即可修改您的联系人姓名、单位名称、微信号,或进行登录密码的修改。',
+  },
+]
+
+export function HelpPage() {
+  const [activeCategory, setActiveCategory] = useState<string>('all')
+  const [searchKeyword, setSearchKeyword] = useState<string>('')
+  const [openId, setOpenId] = useState<string | null>('what-is-xinghen')
+  const [showContact, setShowContact] = useState<boolean>(false)
+
+  const toggleAccordion = (id: string) => {
+    setOpenId((prev) => (prev === id ? null : id))
+  }
+
+  const filteredFaqs = FAQ_LIST.filter((item) => {
+    const matchCategory = activeCategory === 'all' || item.category === activeCategory
+    const q = searchKeyword.trim().toLowerCase()
+    if (!q) return matchCategory
+
+    const questionMatch = item.question.toLowerCase().includes(q)
+    const answerMatch =
+      typeof item.answer === 'string'
+        ? item.answer.toLowerCase().includes(q)
+        : true
+
+    return matchCategory && (questionMatch || answerMatch)
+  })
+
+  return (
+    <div className="page help-page">
+      <Link to="/" className="back-link">
+        <ArrowLeft size={16} /> 返回我的课程
+      </Link>
+
+      {/* 顶部介绍区 */}
+      <section className="help-hero">
+        <div className="help-hero-content">
+          <p className="eyebrow">
+            <Sparkles size={14} /> HELP & DOCUMENTATION
+          </p>
+          <h1>帮助与使用指南</h1>
+          <p className="help-hero-desc">
+            了解星痕智能课程工坊的运作模式、素材规范与制作交付流程,让好课制作更轻松。
+          </p>
+
+          <div className="help-search-bar">
+            <Search size={18} className="search-icon" />
+            <input
+              type="text"
+              placeholder="搜索常见问题,例如:PPT要求、制作周期、讲师照片..."
+              value={searchKeyword}
+              onChange={(e) => setSearchKeyword(e.target.value)}
+            />
+            {searchKeyword && (
+              <button
+                type="button"
+                className="clear-search-btn"
+                onClick={() => setSearchKeyword('')}
+              >
+                清空
+              </button>
+            )}
+          </div>
+        </div>
+      </section>
+
+      {/* 网站介绍与核心能力 */}
+      <section className="help-section">
+        <div className="help-section-header">
+          <span className="section-kicker">ABOUT XINGHEN</span>
+          <h2>认识星痕课程工坊</h2>
+          <p>删去繁复,以工业级标准化流程助力数字化课程高效产出</p>
+        </div>
+
+        <div className="help-feature-grid">
+          <div className="help-feature-card">
+            <div className="feature-icon-box">
+              <Layers size={22} />
+            </div>
+            <h3>结构化大纲编排</h3>
+            <p>
+              支持按集管理课程知识点、逐字稿讲义与预估时长,告别混乱的文档传输与版本断层。
+            </p>
+          </div>
+
+          <div className="help-feature-card">
+            <div className="feature-icon-box">
+              <UserCheck size={22} />
+            </div>
+            <h3>讲师出镜形象配置</h3>
+            <p>
+              支持为每门课程建立专属讲师库,录入站姿与坐姿立绘素材,保障课件出镜形象专业统一。
+            </p>
+          </div>
+
+          <div className="help-feature-card">
+            <div className="feature-icon-box">
+              <UploadCloud size={22} />
+            </div>
+            <h3>全格式课件归集</h3>
+            <p>
+              PPT、PDF、教学讲义集中云端归档,自动匹配分集内容,让每一次制作都有迹可循。
+            </p>
+          </div>
+
+          <div className="help-feature-card">
+            <div className="feature-icon-box">
+              <Video size={22} />
+            </div>
+            <h3>专业制作与成果交付</h3>
+            <p>
+              一键提交进入流水线制作,在线实时跟踪制作状态,成片交付支持在线即时预览与下载。
+            </p>
+          </div>
+        </div>
+      </section>
+
+      {/* 快速入门 4 步流程 */}
+      <section className="help-section">
+        <div className="help-section-header">
+          <span className="section-kicker">WORKFLOW</span>
+          <h2>课程制作四步法</h2>
+          <p>从创意构思到拿到成品成片,只需要清晰的四步</p>
+        </div>
+
+        <div className="help-steps-grid">
+          <div className="help-step-item">
+            <div className="step-badge">01</div>
+            <div className="step-item-body">
+              <h4>创建课程</h4>
+              <p>录入课程定位、受众或直接选择系统提供的官方范例模板一键快速生成。</p>
+            </div>
+          </div>
+
+          <div className="help-step-item">
+            <div className="step-badge">02</div>
+            <div className="step-item-body">
+              <h4>编排大纲与台词</h4>
+              <p>添加分集,填写每集知识主题与口播讲义,设定课程节奏。</p>
+            </div>
+          </div>
+
+          <div className="help-step-item">
+            <div className="step-badge">03</div>
+            <div className="step-item-body">
+              <h4>上传讲师与课件</h4>
+              <p>上传讲师高清照片(坐姿/站姿)及配套 PPT 课件等制作素材。</p>
+            </div>
+          </div>
+
+          <div className="help-step-item">
+            <div className="step-badge">04</div>
+            <div className="step-item-body">
+              <h4>提交制作与交付</h4>
+              <p>确认无误后提交,顾问团队排期制作,完成后在工作区一键获取成品。</p>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* 常见问题 FAQ */}
+      <section className="help-section">
+        <div className="help-section-header">
+          <span className="section-kicker">FAQ & ANSWERS</span>
+          <h2>常见问题解答</h2>
+          <p>遇到疑问?这里整理了大家最常咨询的操作与制作问题</p>
+        </div>
+
+        {/* 分类过滤标签 */}
+        <div className="faq-category-tabs">
+          {CATEGORIES.map((cat) => (
+            <button
+              key={cat.key}
+              type="button"
+              className={`faq-tab-btn ${activeCategory === cat.key ? 'active' : ''}`}
+              onClick={() => setActiveCategory(cat.key)}
+            >
+              {cat.label}
+            </button>
+          ))}
+        </div>
+
+        {/* 问答列表 */}
+        <div className="faq-accordion-list">
+          {filteredFaqs.length === 0 ? (
+            <div className="faq-empty-state">
+              <FileQuestion size={36} />
+              <h4>未找到匹配的问题</h4>
+              <p>试着换个搜索关键词,或直接联系制作顾问获取支持。</p>
+            </div>
+          ) : (
+            filteredFaqs.map((faq) => {
+              const isOpen = openId === faq.id
+              return (
+                <div
+                  key={faq.id}
+                  className={`faq-accordion-item ${isOpen ? 'is-open' : ''}`}
+                >
+                  <button
+                    type="button"
+                    className="faq-question-btn"
+                    onClick={() => toggleAccordion(faq.id)}
+                    aria-expanded={isOpen}
+                  >
+                    <span className="faq-question-text">{faq.question}</span>
+                    <span className="faq-chevron-icon">
+                      {isOpen ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
+                    </span>
+                  </button>
+                  {isOpen && (
+                    <div className="faq-answer-content">
+                      <div className="faq-answer-inner">{faq.answer}</div>
+                    </div>
+                  )}
+                </div>
+              )
+            })
+          )}
+        </div>
+      </section>
+
+      {/* 底部联系支持卡片 */}
+      <section className="help-contact-banner">
+        <div className="contact-banner-content">
+          <div className="contact-banner-icon">
+            <MessageSquare size={28} />
+          </div>
+          <div>
+            <h3>还有其他疑问或需要定制支持?</h3>
+            <p>
+              加入星痕专属制作沟通群,制作顾问与技术团队将为您提供 1 对 1 咨询与进度同步。
+            </p>
+          </div>
+        </div>
+        <div className="contact-banner-action">
+          <button
+            type="button"
+            className="primary-button"
+            onClick={() => setShowContact(true)}
+          >
+            <MessageCircle size={16} /> 联系制作顾问
+          </button>
+        </div>
+      </section>
+
+      {/* 联系顾问弹窗 */}
+      {showContact && <ContactModal onClose={() => setShowContact(false)} />}
+    </div>
+  )
+}

+ 9 - 47
src/pages/NewCourse.tsx

@@ -5,13 +5,10 @@ import {
   Check,
   CheckCircle2,
   FileCheck,
-  FileSpreadsheet,
-  FileText,
   Flame,
   Info,
   Layers,
   Sparkles,
-  UserCheck,
   Users,
   Video,
   Volume2,
@@ -23,6 +20,9 @@ import { Link, useNavigate } from 'react-router-dom'
 import { api } from '../api'
 import { COURSE_TEMPLATES, type CourseTemplate } from '../data/courseTemplates'
 
+const DEFAULT_COURSE_DESCRIPTION = `【课程简介】本课程围绕核心主题展开,通过清晰的知识讲解与案例说明,帮助学习者理解并掌握课程重点。
+【制作要求】采用“讲师讲解 + PPT课件”的形式,画面简洁清晰,内容层次分明;讲解自然流畅,重点内容配合字幕或图示呈现;视频建议使用16:9横屏、1080P高清规格。`
+
 export function NewCourse() {
   const navigate = useNavigate()
   const [templates, setTemplates] = useState<CourseTemplate[]>(COURSE_TEMPLATES)
@@ -32,7 +32,7 @@ export function NewCourse() {
   // 表单状态
   const [name, setName] = useState('')
   const [category, setCategory] = useState('党政专题')
-  const [description, setDescription] = useState('')
+  const [description, setDescription] = useState(DEFAULT_COURSE_DESCRIPTION)
 
   const [busy, setBusy] = useState(false)
   const [error, setError] = useState('')
@@ -113,7 +113,7 @@ export function NewCourse() {
             这是一门什么课。
           </h1>
           <p className="aside-subtext">
-            填写基本信息后,可添加课程资产和讲师;您也可以直接选择下方官方示范任务书一键初始化
+            填写基本信息后,可继续添加课程资产和讲师;也可以参考下方制作示例快速开始
           </p>
 
           {/* 示范模版精选卡片 */}
@@ -123,7 +123,6 @@ export function NewCourse() {
                 <Flame size={16} className="flame-icon" />
                 <strong>制作示例</strong>
               </div>
-              <span className="sample-count-tag">{templates.length} 个模版可直接使用</span>
             </div>
 
             {/* 模板切换标签(支持未来扩展) */}
@@ -144,13 +143,6 @@ export function NewCourse() {
             {/* 当前选中模版内容卡片 */}
             <div className="selected-template-card">
               <div className="card-top">
-                <div className="card-tags">
-                  {currentTemplate.tags.map((tag, idx) => (
-                    <span key={idx} className="sample-tag">
-                      {tag}
-                    </span>
-                  ))}
-                </div>
                 <h3 className="sample-title">{currentTemplate.name}</h3>
                 <p className="sample-desc">{currentTemplate.summary}</p>
               </div>
@@ -178,28 +170,6 @@ export function NewCourse() {
                     <span>{currentTemplate.keySpecs.speechSpeed}</span>
                   </div>
                 </div>
-                <div className="spec-item">
-                  <UserCheck size={13} />
-                  <div>
-                    <small>预置示范讲师</small>
-                    <span>{currentTemplate.instructors.map((i) => i.name).join('、')}</span>
-                  </div>
-                </div>
-              </div>
-
-              {/* 预置素材与任务书一览 */}
-              <div className="preset-assets-strip">
-                <div className="strip-title">
-                  <FileSpreadsheet size={13} />
-                  <span>包含课件与制作资料 ({currentTemplate.assets.length} 份)</span>
-                </div>
-                <div className="preset-pill-list">
-                  {currentTemplate.assets.map((ast, i) => (
-                    <span key={i} className="asset-minitag" title={ast.description}>
-                      <FileText size={11} /> {ast.originalName}
-                    </span>
-                  ))}
-                </div>
               </div>
 
               {/* 模版操作按钮组 */}
@@ -251,30 +221,22 @@ export function NewCourse() {
                 <span>COURSE PROFILE</span>
                 <h2>课程基本信息</h2>
               </div>
-              <button
-                type="button"
-                className="quick-sample-pill-btn"
-                onClick={() => handleFillTemplate(currentTemplate)}
-                title="快速载入精选示范内容"
-              >
-                <Wand2 size={13} /> 载入精选示例
-              </button>
             </div>
 
             <label>
-              课程名称 <b>*</b>
+              <span>课程名称 <b>*</b></span>
               <input
                 name="name"
                 required
                 maxLength={100}
-                placeholder="例如:20260715《坚持人民至上根本立场党课视频"
+                placeholder="例如:坚持人民至上根本立场党课视频"
                 value={name}
                 onChange={(e) => setName(e.target.value)}
               />
             </label>
 
             <label>
-              课程分类
+              <span>课程分类</span>
               <select
                 name="category"
                 value={category}
@@ -289,7 +251,7 @@ export function NewCourse() {
             </label>
 
             <label>
-              课程简介与制作要求
+              <span>课程简介与制作要求</span>
               <textarea
                 name="description"
                 rows={7}

+ 2 - 0
src/pages/admin/AdminLayout.tsx

@@ -10,6 +10,7 @@ import {
 } from 'lucide-react'
 import { NavLink, Navigate, Outlet, useNavigate } from 'react-router-dom'
 import { Brand } from '../../components/Brand'
+import { Footer } from '../../components/Footer'
 import { useAuth } from '../../AuthContext'
 
 export function AdminLayout() {
@@ -74,6 +75,7 @@ export function AdminLayout() {
       <main className="admin-main">
         <Outlet />
       </main>
+      <Footer />
     </div>
   )
 }

+ 1 - 7
src/pages/admin/AdminLoginPage.tsx

@@ -1,4 +1,4 @@
-import { ArrowRight, LockKeyhole, Phone, ShieldCheck, Sparkles } from 'lucide-react'
+import { ArrowRight, LockKeyhole, Phone, ShieldCheck } from 'lucide-react'
 import { FormEvent, useState } from 'react'
 import { Link, Navigate, useNavigate } from 'react-router-dom'
 import { Brand } from '../../components/Brand'
@@ -56,7 +56,6 @@ export function AdminLoginPage() {
                 required
                 pattern="1[0-9]{10}"
                 placeholder="请输入管理员手机号"
-                defaultValue="18888888888"
               />
             </div>
           </label>
@@ -71,7 +70,6 @@ export function AdminLoginPage() {
                 required
                 minLength={6}
                 placeholder="请输入密码"
-                defaultValue="admin123456"
               />
             </div>
           </label>
@@ -85,10 +83,6 @@ export function AdminLoginPage() {
         </form>
 
         <div className="admin-login-footer">
-          <div className="admin-tip-box">
-            <Sparkles size={14} />
-            <span>默认管理员:18888888888 / admin123456</span>
-          </div>
           <Link to="/login" className="back-to-client-link">
             ← 返回普通创作者登录
           </Link>

+ 992 - 98
src/styles.css

@@ -5,19 +5,19 @@ button,input,textarea,select { font:inherit; }
 button,a { -webkit-tap-highlight-color:transparent; }
 a { color:inherit; text-decoration:none; }
 button { cursor:pointer; }
-button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); outline-offset:2px; }
-.app-shell { min-height:100vh; }
+.app-shell { min-height:100vh; display:flex; flex-direction:column; }
+.app-main { flex:1; }
 .topbar { height:72px; padding:0 clamp(24px,5vw,76px); display:flex; align-items:center; border-bottom:1px solid rgba(220,225,235,.8); background:rgba(248,249,252,.82); backdrop-filter:blur(14px); position:sticky; top:0; z-index:20; }
 .logo-link { display:flex; }
-.brand { display:flex; align-items:center; gap:13px; min-width:172px; }
-.brand>span:last-child { display:flex; align-items:baseline; gap:8px; }
-.brand strong { font-size:22px; letter-spacing:.18em; font-weight:500; color:#101522; }
-.brand small { color:#9da4b3; font-size:9px; letter-spacing:.22em; }
-.brand-mark { width:42px; height:26px; position:relative; display:inline-block; overflow:hidden; }
-.brand-mark:before,.brand-mark:after { content:''; position:absolute; border:1px solid #627087; border-color:#627087 transparent transparent transparent; border-radius:50%; width:46px; height:24px; left:1px; top:9px; transform:rotate(-6deg); }
-.brand-mark:after { border-color:#aeb6c5 transparent transparent transparent; width:38px; height:17px; left:7px; top:15px; }
-.brand-mark i,.brand-mark b { width:5px; height:5px; border-radius:50%; position:absolute; z-index:2; }
-.brand-mark i { background:#c4a05b; left:0; top:14px; }.brand-mark b { background:#344159; right:1px; top:12px; }
+.brand { display:inline-flex; align-items:center; gap:11px; text-decoration:none; user-select:none; }
+.brand-mark { width:38px; height:24px; display:inline-flex; align-items:center; justify-content:center; flex-shrink:0; transition:transform .3s cubic-bezier(0.2,0.8,0.2,1); }
+.brand-mark-svg { width:100%; height:100%; overflow:visible; display:block; }
+.brand:hover .brand-mark { transform:translateY(-1px) scale(1.04); }
+.brand-text { display:inline-flex; align-items:baseline; gap:8px; }
+.brand strong, .brand-title { font-family:"Songti SC","Noto Serif SC","Source Han Serif CN","STSong","STKaiti","PingFang SC",serif; font-size:21px; font-weight:600; letter-spacing:.2em; color:#121826; line-height:1; transition:color .2s ease; }
+.brand small, .brand-sub { font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; font-size:9px; font-weight:500; letter-spacing:.28em; color:#8c97aa; line-height:1; text-transform:uppercase; }
+.brand--compact .brand-mark { width:32px; height:20px; }
+.brand--compact strong, .brand--compact .brand-title { font-size:18px; letter-spacing:.16em; }
 .topbar nav { display:flex; height:100%; gap:34px; align-items:center; margin-left:60px; color:#9aa3b3; font-size:14px; }
 .topbar nav a { color:#5b6880; font-weight:500; height:100%; display:flex; align-items:center; position:relative; }
 .topbar nav a.active { color:var(--blue); font-weight:600; }
@@ -94,7 +94,8 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .empty-state { background:rgba(255,255,255,.58); border:1px dashed #d9dfe9; min-height:260px; border-radius:12px; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:30px; }
 .empty-state h3 { margin:13px 0 5px; font-size:17px; font-weight:500; }
 .empty-state p { color:#939cad; font-size:13px; margin:0 0 15px; }
-.empty-state a { color:var(--blue); font-size:13px; display:flex; align-items:center; gap:5px; }
+.empty-state a:not([class*="button"]) { color:var(--blue); font-size:13px; display:flex; align-items:center; gap:5px; }
+.empty-state .primary-button { color:#fff; }
 .compact-empty { min-height:160px; padding:20px; }
 .empty-orbit { width:58px; height:33px; border:1px solid #aab4c4; border-color:#aab4c4 transparent transparent transparent; border-radius:50%; position:relative; transform:rotate(-9deg); }
 .empty-orbit i { position:absolute; width:6px; height:6px; background:#c4a05b; border-radius:50%; top:8px; left:-1px; }
@@ -115,7 +116,8 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .form-title h3 { font-size:19px; margin:6px 0 0; font-weight:500; }
 .form-title h4 { font-size:16px; margin:4px 0 0; font-weight:500; }
 .paper-form label { display:flex; flex-direction:column; gap:9px; font-size:12px; color:#59657b; margin:0 0 22px; }
-.paper-form label b { color:#d86f67; }
+.paper-form label span { display:inline-flex; align-items:center; gap:3px; }
+.paper-form label b { color:#d86f67; font-weight:600; }
 .paper-form input,.paper-form select,.paper-form textarea { border:1px solid #dfe4ec; border-radius:7px; color:#253149; background:#fbfcfe; padding:12px 13px; outline:none; width:100%; transition:.2s; }
 .paper-form textarea { resize:vertical; line-height:1.6; }
 .paper-form input:focus,.paper-form select:focus,.paper-form textarea:focus { border-color:#7599e8; box-shadow:0 0 0 3px rgba(36,91,214,.08); background:#fff; }
@@ -1792,10 +1794,32 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   border-left: 3px solid #3b82f6;
 }
 
+.episode-content-heading {
+  display: flex;
+  align-items: center;
+  margin: 2px 0 12px;
+}
+
+.episode-content-heading > div {
+  display: flex;
+  align-items: baseline;
+  gap: 10px;
+}
+
+.episode-content-heading strong {
+  color: #1e293b;
+  font-size: 14px;
+}
+
+.episode-content-heading span {
+  color: #94a3b8;
+  font-size: 12px;
+}
+
 .ep-body-grid {
-  display: grid;
-  grid-template-columns: 1.1fr 0.9fr;
-  gap: 18px;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
 }
 
 .ep-notes-box,
@@ -1808,6 +1832,10 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   padding: 14px 16px;
 }
 
+.ep-assets-box {
+  padding-bottom: 12px;
+}
+
 .box-title-row {
   display: flex;
   justify-content: space-between;
@@ -1825,6 +1853,21 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   font-weight: 600;
 }
 
+.optional-label,
+.asset-count-label {
+  padding: 2px 6px;
+  border-radius: 999px;
+  background: #f1f5f9;
+  color: #64748b;
+  font-size: 10px;
+  font-weight: 500;
+}
+
+.asset-count-label {
+  background: #eff6ff;
+  color: #2563eb;
+}
+
 .dirty-badge {
   font-size: 11px;
   padding: 2px 6px;
@@ -1877,7 +1920,7 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   display: flex;
   flex-direction: column;
   gap: 6px;
-  margin-bottom: 12px;
+  margin-bottom: 0;
   max-height: 160px;
   overflow-y: auto;
 }
@@ -1925,7 +1968,7 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 }
 
 .ep-upload-zone {
-  margin-top: auto;
+  margin-top: 8px;
 }
 
 .ep-file-label {
@@ -1949,6 +1992,12 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   border-color: #60a5fa;
 }
 
+.compact-upload-button {
+  flex: 0 0 auto;
+  padding: 6px 11px;
+  border-style: solid;
+}
+
 .ep-pending-files {
   margin-top: 8px;
   display: flex;
@@ -2619,22 +2668,23 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
   color: #64748b;
 }
 
-/* ==================== 危险操作区 (Danger Zone) & 删除课程弹窗 ==================== */
+/* ==================== 底部辅助操作区 & 删除课程弹窗 ==================== */
 .course-danger-section {
-  margin-top: 36px;
+  margin-top: 40px;
   margin-bottom: 24px;
+  padding-top: 24px;
+  border-top: 1px dashed #e2e8f0;
 }
 
 .danger-zone-card {
-  background: #ffffff;
-  border: 1px solid #fee2e2;
-  border-radius: 14px;
-  padding: 20px 24px;
+  background: #fbfcfd;
+  border: 1px solid #f1f5f9;
+  border-radius: 10px;
+  padding: 14px 18px;
   display: flex;
   justify-content: space-between;
   align-items: center;
-  gap: 20px;
-  box-shadow: 0 2px 10px rgba(239, 68, 68, 0.04);
+  gap: 16px;
 }
 
 @media (max-width: 640px) {
@@ -2647,63 +2697,61 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .danger-zone-title-row {
   display: flex;
   align-items: center;
-  gap: 8px;
-  margin-bottom: 4px;
+  gap: 6px;
+  margin-bottom: 2px;
 }
 
 .danger-icon {
-  color: #dc2626;
+  color: #94a3b8;
 }
 
 .danger-zone-info h4 {
   margin: 0;
-  font-size: 15px;
-  font-weight: 600;
-  color: #991b1b;
+  font-size: 13.5px;
+  font-weight: 500;
+  color: #475569;
 }
 
 .danger-zone-info p {
   margin: 0;
-  font-size: 13px;
-  color: #64748b;
+  font-size: 12px;
+  color: #94a3b8;
   line-height: 1.5;
 }
 
-.danger-button {
-  background: #ef4444;
-  color: #ffffff;
-  border: 1px solid #dc2626;
-  border-radius: var(--button-radius, 8px);
-  min-height: 38px;
-  padding: 0 18px;
-  font-size: 13px;
-  font-weight: 500;
+.danger-button-subtle {
+  background: transparent;
+  color: #64748b;
+  border: 1px solid #e2e8f0;
+  border-radius: var(--button-radius, 6px);
+  min-height: 32px;
+  padding: 0 12px;
+  font-size: 12px;
+  font-weight: 400;
   display: inline-flex;
   align-items: center;
   justify-content: center;
-  gap: 7px;
+  gap: 6px;
   cursor: pointer;
   white-space: nowrap;
-  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
-  box-shadow: 0 2px 8px rgba(239, 68, 68, 0.22);
+  transition: all 0.2s ease;
 }
 
-.danger-button:hover:not(:disabled) {
-  background: #dc2626;
-  border-color: #b91c1c;
-  transform: translateY(-1px);
-  box-shadow: 0 4px 12px rgba(220, 38, 38, 0.28);
+.danger-button-subtle:hover:not(:disabled) {
+  background: #fef2f2;
+  color: #ef4444;
+  border-color: #fecaca;
 }
 
-.danger-button:active:not(:disabled) {
-  transform: translateY(0);
+.danger-button-subtle:active:not(:disabled) {
+  background: #fee2e2;
 }
 
-.danger-button:disabled,
-.danger-button.disabled {
-  background: #f1f5f9 !important;
-  border-color: #e2e8f0 !important;
-  color: #94a3b8 !important;
+.danger-button-subtle:disabled,
+.danger-button-subtle.disabled {
+  background: transparent !important;
+  border-color: #f1f5f9 !important;
+  color: #cbd5e1 !important;
   opacity: 0.8 !important;
   cursor: not-allowed !important;
   box-shadow: none !important;
@@ -2714,53 +2762,32 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .modal-dialog.delete-course-modal {
   width: min(480px, 95vw);
   background: #ffffff;
-  border-radius: 16px;
-  box-shadow: 0 25px 60px rgba(0, 0, 0, 0.2);
+  border-radius: 14px;
+  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
   display: flex;
   flex-direction: column;
   overflow: hidden;
-  border: 1px solid #fee2e2;
+  border: 1px solid #e2e8f0;
   animation: modalPop 0.22s cubic-bezier(0.16, 1, 0.3, 1);
 }
 
-.modal-header.danger-header {
-  background: #fff5f5;
-  border-bottom: 1px solid #fee2e2;
-  padding: 18px 22px;
-}
-
-.danger-header-title {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-
-.danger-icon-badge {
-  width: 38px;
-  height: 38px;
-  border-radius: 50%;
-  background: #fee2e2;
-  color: #dc2626;
-  display: grid;
-  place-items: center;
-  flex-shrink: 0;
-}
-
-.modal-badge.danger-badge {
-  background: #fee2e2;
-  color: #b91c1c;
+.modal-header-simple-title h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #1e293b;
 }
 
 .delete-modal-body {
-  padding: 20px 22px;
+  padding: 18px 22px;
   display: flex;
   flex-direction: column;
   gap: 16px;
 }
 
 .danger-alert-card {
-  background: #fff5f5;
-  border: 1px solid #fed7d7;
+  background: #fff8f8;
+  border: 1px solid #fee2e2;
   border-radius: 8px;
   padding: 12px 14px;
 }
@@ -2768,7 +2795,7 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .danger-alert-card p {
   margin: 0;
   font-size: 13px;
-  color: #742a2a;
+  color: #991b1b;
   line-height: 1.55;
 }
 
@@ -2791,11 +2818,11 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 
 .delete-confirm-input {
   width: 100%;
-  height: 40px;
-  border-radius: 8px;
-  border: 1.5px solid #cbd5e1;
+  height: 38px;
+  border-radius: 6px;
+  border: 1px solid #cbd5e1;
   padding: 0 12px;
-  font-size: 14px;
+  font-size: 13.5px;
   background: #ffffff;
   color: #0f172a;
   transition: border-color 0.2s, box-shadow 0.2s;
@@ -2805,11 +2832,878 @@ button:focus-visible,a:focus-visible { outline:3px solid rgba(36,91,214,.22); ou
 .delete-confirm-input:focus {
   border-color: #ef4444;
   outline: none;
-  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
+  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);
 }
 
 .danger-submit-btn {
-  min-height: 38px;
-  padding: 0 20px;
+  background: #ef4444;
+  color: #ffffff;
+  border: 1px solid #dc2626;
+  border-radius: var(--button-radius, 6px);
+  min-height: 36px;
+  padding: 0 18px;
+  font-size: 13px;
+  font-weight: 500;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  cursor: pointer;
+  white-space: nowrap;
+  transition: all 0.2s ease;
+}
+
+.danger-submit-btn:hover:not(:disabled) {
+  background: #dc2626;
+  border-color: #b91c1c;
+}
+
+.danger-submit-btn:disabled {
+  background: #f1f5f9 !important;
+  border-color: #e2e8f0 !important;
+  color: #94a3b8 !important;
+  opacity: 0.8 !important;
+  cursor: not-allowed !important;
+}
+
+/* ==================== 联系星痕 · 等待制作面板卡片样式 ==================== */
+.success-panel-actions {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 14px;
+  margin-top: 32px;
+}
+
+.contact-support-card {
+  margin-top: 28px;
+  background: #f8fafc;
+  border: 1px solid #e2e8f0;
+  border-radius: 12px;
+  padding: 22px 24px;
+  text-align: left;
+}
+
+.contact-card-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #edf2f7;
+}
+
+.contact-icon-badge {
+  width: 38px;
+  height: 38px;
+  border-radius: 8px;
+  background: #eff6ff;
+  color: #2563eb;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.contact-header-text h4 {
+  margin: 0 0 4px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #0f172a;
+}
+
+.contact-header-text p {
+  margin: 0;
+  font-size: 12px;
+  color: #64748b;
+  line-height: 1.5;
+}
+
+.contact-card-body {
+  display: flex;
+  gap: 22px;
+  align-items: stretch;
+}
+
+.contact-qr-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  background: #ffffff;
+  padding: 12px;
+  border-radius: 10px;
+  border: 1px solid #e2e8f0;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03);
+  flex-shrink: 0;
+}
+
+.contact-qr-box {
+  width: 140px;
+  height: 140px;
+  border-radius: 6px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #ffffff;
+}
+
+.contact-qr-image {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  display: block;
+}
+
+.contact-qr-caption {
+  font-size: 11px;
+  font-weight: 500;
+  color: #64748b;
+}
+
+.contact-card-steps {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  gap: 10px;
+}
+
+.contact-step-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  padding: 10px 14px;
+  background: #ffffff;
+  border: 1px solid #f1f5f9;
+  border-radius: 8px;
+}
+
+.step-circle {
+  width: 22px;
+  height: 22px;
+  border-radius: 50%;
+  background: #eff6ff;
+  color: #2563eb;
+  font-size: 11px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  margin-top: 1px;
+}
+
+.step-desc {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
 }
 
+.step-desc strong {
+  font-size: 13px;
+  font-weight: 600;
+  color: #1e293b;
+}
+
+.step-desc span {
+  font-size: 12px;
+  color: #64748b;
+  line-height: 1.4;
+}
+
+/* ==================== 联系星痕 · 弹窗样式 ==================== */
+.modal-dialog.contact-modal {
+  width: min(560px, 95vw);
+  background: #ffffff;
+  border-radius: 14px;
+  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.15);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  border: 1px solid #e2e8f0;
+  animation: modalPop 0.22s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.contact-modal-body {
+  padding: 20px 24px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.contact-modal-intro {
+  background: #f8fafc;
+  border: 1px solid #e2e8f0;
+  border-radius: 8px;
+  padding: 12px 16px;
+  font-size: 13px;
+  color: #475569;
+  line-height: 1.6;
+}
+
+.contact-modal-intro p {
+  margin: 0;
+}
+
+.contact-modal-intro strong {
+  color: #0f172a;
+}
+
+.contact-modal-qr-section {
+  display: flex;
+  gap: 16px;
+  align-items: stretch;
+}
+
+.contact-modal-qr-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  background: #ffffff;
+  padding: 12px;
+  border-radius: 10px;
+  border: 1px solid #e2e8f0;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
+  flex-shrink: 0;
+}
+
+.qr-image-wrapper {
+  width: 140px;
+  height: 140px;
+  border-radius: 6px;
+  overflow: hidden;
+  background: #ffffff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.contact-modal-qr-img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  display: block;
+}
+
+.qr-badge-text {
+  display: inline-flex;
+  align-items: center;
+  gap: 5px;
+  font-size: 11px;
+  font-weight: 500;
+  color: #64748b;
+}
+
+.contact-modal-steps {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.contact-step-card {
+  display: flex;
+  align-items: flex-start;
+  gap: 10px;
+  padding: 8px 12px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #f1f5f9;
+}
+
+.step-badge-num {
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  background: #eff6ff;
+  color: #2563eb;
+  font-size: 11px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  margin-top: 1px;
+}
+
+.step-content strong {
+  display: block;
+  font-size: 12.5px;
+  font-weight: 600;
+  color: #1e293b;
+  margin-bottom: 2px;
+}
+
+.step-content p {
+  margin: 0;
+  font-size: 11.5px;
+  color: #64748b;
+  line-height: 1.4;
+}
+
+.contact-modal-footer-tip {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #475569;
+  background: #eff6ff;
+  border: 1px solid #dbeafe;
+  padding: 10px 14px;
+  border-radius: 8px;
+}
+
+.contact-modal-footer-tip svg {
+  color: #3b82f6;
+  flex-shrink: 0;
+}
+
+@media (max-width: 600px) {
+  .contact-card-body,
+  .contact-modal-qr-section {
+    flex-direction: column;
+    align-items: center;
+  }
+  .contact-card-steps,
+  .contact-modal-steps {
+    width: 100%;
+  }
+}
+
+/* ==========================================================================
+   帮助中心 (Help & FAQ Page)
+   ========================================================================== */
+
+.help-page {
+  padding-top: 32px;
+  max-width: 1100px;
+}
+
+.help-hero {
+  text-align: center;
+  padding: 30px 20px 48px;
+  margin-bottom: 24px;
+  border-bottom: 1px solid var(--line);
+  position: relative;
+}
+
+.help-hero-content {
+  max-width: 680px;
+  margin: 0 auto;
+}
+
+.help-hero-content .eyebrow {
+  justify-content: center;
+  margin-bottom: 14px !important;
+}
+
+.help-hero h1 {
+  font-size: clamp(28px, 3.6vw, 42px);
+  font-weight: 400;
+  color: var(--ink);
+  margin: 0 0 14px;
+  letter-spacing: -0.02em;
+}
+
+.help-hero-desc {
+  color: var(--muted);
+  font-size: 15px;
+  line-height: 1.7;
+  margin: 0 auto 32px;
+  max-width: 540px;
+}
+
+/* 搜索栏 */
+.help-search-bar {
+  position: relative;
+  max-width: 580px;
+  margin: 0 auto;
+  display: flex;
+  align-items: center;
+}
+
+.help-search-bar .search-icon {
+  position: absolute;
+  left: 18px;
+  color: #94a3b8;
+  pointer-events: none;
+}
+
+.help-search-bar input {
+  width: 100%;
+  height: 52px;
+  padding: 0 70px 0 50px;
+  font-size: 15px;
+  background: #fff;
+  border: 1px solid #dbe2ee;
+  border-radius: 26px;
+  color: var(--ink);
+  box-shadow: 0 4px 20px rgba(36, 91, 214, 0.05);
+  transition: all 0.2s ease;
+  outline: none;
+}
+
+.help-search-bar input:focus {
+  border-color: var(--blue);
+  box-shadow: 0 0 0 4px rgba(36, 91, 214, 0.1);
+}
+
+.clear-search-btn {
+  position: absolute;
+  right: 16px;
+  background: none;
+  border: none;
+  font-size: 12px;
+  color: var(--muted);
+  padding: 4px 8px;
+  border-radius: 4px;
+}
+
+.clear-search-btn:hover {
+  color: var(--ink);
+  background: #f1f5f9;
+}
+
+/* 页面小节通用头部 */
+.help-section {
+  margin-bottom: 56px;
+}
+
+.help-section-header {
+  text-align: center;
+  margin-bottom: 32px;
+}
+
+.help-section-header h2 {
+  font-size: 24px;
+  font-weight: 500;
+  color: var(--ink);
+  margin: 6px 0 8px;
+}
+
+.help-section-header p {
+  color: var(--muted);
+  font-size: 14px;
+  margin: 0;
+}
+
+/* 核心特性 4 卡片 */
+.help-feature-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 18px;
+}
+
+.help-feature-card {
+  background: #fff;
+  border: 1px solid var(--line);
+  border-radius: 12px;
+  padding: 24px 20px;
+  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+  display: flex;
+  flex-direction: column;
+}
+
+.help-feature-card:hover {
+  border-color: #cbd5e1;
+  transform: translateY(-2px);
+  box-shadow: 0 10px 28px rgba(30, 41, 59, 0.06);
+}
+
+.feature-icon-box {
+  width: 44px;
+  height: 44px;
+  border-radius: 10px;
+  background: #edf3ff;
+  color: var(--blue);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16px;
+}
+
+.help-feature-card h3 {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--ink);
+  margin: 0 0 8px;
+}
+
+.help-feature-card p {
+  font-size: 13px;
+  color: #64748b;
+  line-height: 1.65;
+  margin: 0;
+}
+
+/* 四步流程指南 */
+.help-steps-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
+}
+
+.help-step-item {
+  background: #fff;
+  border: 1px solid var(--line);
+  border-radius: 12px;
+  padding: 22px 18px;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.step-badge {
+  font-size: 20px;
+  font-weight: 700;
+  color: #93c5fd;
+  font-family: Inter, monospace;
+  margin-bottom: 12px;
+  line-height: 1;
+}
+
+.step-item-body h4 {
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--ink);
+  margin: 0 0 6px;
+}
+
+.step-item-body p {
+  font-size: 12.5px;
+  color: #64748b;
+  line-height: 1.6;
+  margin: 0;
+}
+
+/* FAQ 分类切换 Tabs */
+.faq-category-tabs {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 24px;
+}
+
+.faq-tab-btn {
+  background: #fff;
+  border: 1px solid #dce2ed;
+  color: #4b5563;
+  padding: 8px 18px;
+  border-radius: 20px;
+  font-size: 13px;
+  font-weight: 500;
+  transition: all 0.2s ease;
+}
+
+.faq-tab-btn:hover {
+  border-color: #cbd5e1;
+  background: #f8fafc;
+  color: var(--ink);
+}
+
+.faq-tab-btn.active {
+  background: var(--blue);
+  color: #fff;
+  border-color: var(--blue);
+  box-shadow: 0 4px 12px rgba(36, 91, 214, 0.2);
+}
+
+/* FAQ 手风琴列表 */
+.faq-accordion-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  max-width: 860px;
+  margin: 0 auto;
+}
+
+.faq-accordion-item {
+  background: #fff;
+  border: 1px solid var(--line);
+  border-radius: 10px;
+  overflow: hidden;
+  transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+.faq-accordion-item:hover {
+  border-color: #cbd5e1;
+}
+
+.faq-accordion-item.is-open {
+  border-color: #93c5fd;
+  box-shadow: 0 4px 18px rgba(36, 91, 214, 0.06);
+}
+
+.faq-question-btn {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  text-align: left;
+  padding: 18px 22px;
+  background: transparent;
+  border: none;
+  font-size: 15px;
+  font-weight: 500;
+  color: var(--ink);
+  gap: 16px;
+  transition: background 0.15s ease;
+}
+
+.faq-question-btn:hover {
+  background: #f8fafc;
+}
+
+.faq-question-text {
+  flex: 1;
+}
+
+.faq-chevron-icon {
+  color: #94a3b8;
+  display: grid;
+  place-items: center;
+  flex-shrink: 0;
+  transition: color 0.15s ease;
+}
+
+.faq-accordion-item.is-open .faq-chevron-icon {
+  color: var(--blue);
+}
+
+.faq-answer-content {
+  border-top: 1px solid #f1f5f9;
+  background: #fafcff;
+  padding: 18px 22px;
+  font-size: 14px;
+  color: #475569;
+  line-height: 1.75;
+}
+
+.faq-answer-inner {
+  max-width: 100%;
+}
+
+.faq-empty-state {
+  text-align: center;
+  padding: 48px 20px;
+  background: #fff;
+  border: 1px dashed #cbd5e1;
+  border-radius: 12px;
+  color: #94a3b8;
+}
+
+.faq-empty-state svg {
+  margin-bottom: 12px;
+  color: #94a3b8;
+}
+
+.faq-empty-state h4 {
+  font-size: 16px;
+  color: var(--ink);
+  margin: 0 0 6px;
+  font-weight: 500;
+}
+
+.faq-empty-state p {
+  font-size: 13px;
+  margin: 0;
+}
+
+/* 底部联系卡片 Banner */
+.help-contact-banner {
+  background: linear-gradient(135deg, #ffffff 0%, #edf4ff 100%);
+  border: 1px solid #d4e2fc;
+  border-radius: 14px;
+  padding: 30px 36px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 24px;
+  box-shadow: 0 8px 30px rgba(36, 91, 214, 0.06);
+  margin-top: 20px;
+}
+
+.contact-banner-content {
+  display: flex;
+  align-items: center;
+  gap: 18px;
+}
+
+.contact-banner-icon {
+  width: 52px;
+  height: 52px;
+  border-radius: 12px;
+  background: #fff;
+  color: var(--blue);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  box-shadow: 0 4px 14px rgba(36, 91, 214, 0.12);
+}
+
+.contact-banner-content h3 {
+  font-size: 17px;
+  font-weight: 600;
+  color: var(--ink);
+  margin: 0 0 6px;
+}
+
+.contact-banner-content p {
+  font-size: 13.5px;
+  color: #64748b;
+  margin: 0;
+  line-height: 1.5;
+}
+
+.contact-banner-action {
+  flex-shrink: 0;
+}
+
+/* 响应式断点 */
+@media (max-width: 960px) {
+  .help-feature-grid,
+  .help-steps-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
+  .help-contact-banner {
+    flex-direction: column;
+    text-align: center;
+    padding: 28px 24px;
+  }
+  .contact-banner-content {
+    flex-direction: column;
+  }
+}
+
+@media (max-width: 600px) {
+  .help-feature-grid,
+  .help-steps-grid {
+    grid-template-columns: 1fr;
+  }
+  .help-hero {
+    padding: 20px 10px 36px;
+  }
+  .faq-question-btn {
+    padding: 15px 16px;
+    font-size: 14px;
+  }
+  .faq-answer-content {
+    padding: 15px 16px;
+    font-size: 13.5px;
+  }
+}
+
+/* ==========================================================================
+   全站页脚 (Site Footer)
+   ========================================================================== */
+
+.auth-wrapper {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.auth-wrapper .auth-page {
+  flex: 1;
+  min-height: auto;
+}
+
+.admin-shell {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.admin-main {
+  flex: 1;
+}
+
+.site-footer {
+  border-top: 1px solid rgba(226, 232, 240, 0.7);
+  background: rgba(248, 249, 252, 0.6);
+  backdrop-filter: blur(8px);
+  padding: 16px clamp(24px, 5vw, 76px);
+  margin-top: auto;
+}
+
+.site-footer-inner {
+  max-width: 1400px;
+  margin: 0 auto;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 14px;
+  flex-wrap: wrap;
+  font-size: 11.5px;
+  color: #94a3b8;
+  letter-spacing: 0.01em;
+}
+
+.site-footer-company {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  font-weight: 400;
+  color: #94a3b8;
+}
+
+.site-footer-dot {
+  color: #cbd5e1;
+  user-select: none;
+}
+
+.site-footer-links {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.site-footer-beian {
+  color: #94a3b8;
+  text-decoration: none;
+  transition: color 0.2s ease;
+}
+
+.site-footer-beian:hover {
+  color: #475569;
+  text-decoration: underline;
+}
+
+.site-footer-sep {
+  color: #e2e8f0;
+  user-select: none;
+}
+
+.site-footer-help-link {
+  color: #64748b;
+  font-weight: 400;
+  text-decoration: none;
+  transition: color 0.2s ease;
+}
+
+.site-footer-help-link:hover {
+  color: var(--blue);
+  text-decoration: underline;
+}
+
+@media (max-width: 640px) {
+  .site-footer {
+    padding: 14px 16px;
+  }
+  .site-footer-inner {
+    flex-direction: column;
+    justify-content: center;
+    text-align: center;
+    gap: 6px;
+    font-size: 11px;
+  }
+}
+
+
+

+ 1 - 1
tsconfig.app.tsbuildinfo

@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/authcontext.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/addinstructormodal.tsx","./src/components/brand.tsx","./src/components/deletecoursemodal.tsx","./src/components/episodemodal.tsx","./src/components/instructorimagefield.tsx","./src/components/layout.tsx","./src/components/statusbadge.tsx","./src/data/coursetemplates.ts","./src/pages/authpage.tsx","./src/pages/courseworkspace.tsx","./src/pages/dashboard.tsx","./src/pages/newcourse.tsx","./src/pages/profile.tsx","./src/pages/admin/admincoursedetail.tsx","./src/pages/admin/admincourses.tsx","./src/pages/admin/admindashboard.tsx","./src/pages/admin/adminlayout.tsx","./src/pages/admin/adminloginpage.tsx","./src/pages/admin/adminusers.tsx"],"version":"5.7.3"}
+{"root":["./src/app.tsx","./src/authcontext.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/addinstructormodal.tsx","./src/components/brand.tsx","./src/components/contactmodal.tsx","./src/components/deletecoursemodal.tsx","./src/components/episodemodal.tsx","./src/components/footer.tsx","./src/components/instructorimagefield.tsx","./src/components/layout.tsx","./src/components/statusbadge.tsx","./src/data/coursetemplates.ts","./src/pages/authpage.tsx","./src/pages/courseworkspace.tsx","./src/pages/dashboard.tsx","./src/pages/helppage.tsx","./src/pages/newcourse.tsx","./src/pages/profile.tsx","./src/pages/admin/admincoursedetail.tsx","./src/pages/admin/admincourses.tsx","./src/pages/admin/admindashboard.tsx","./src/pages/admin/adminlayout.tsx","./src/pages/admin/adminloginpage.tsx","./src/pages/admin/adminusers.tsx"],"version":"5.7.3"}