2827 lines
158 KiB
HTML
2827 lines
158 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>笔临 - 长篇创作智能协作平台</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<style>
|
||
:root {
|
||
--bg-primary: #1c1816; --bg-secondary: #201c1a; --bg-tertiary: #262220;
|
||
--bg-surface: #2a2624; --bg-hover: #302c28; --bg-active: #363230; --bg-card: #221e1c;
|
||
--text-primary: #e8e0d8; --text-secondary: #b8aca0; --text-muted: #786858; --text-dim: #584838;
|
||
--accent: #c0504a; --accent-glow: rgba(192,80,74,0.25); --accent-light: #d4706a; --accent-dark: #a0403a;
|
||
--gold: #c8a050; --gold-dim: #786030; --red: #c0504a; --red-dim: #703030;
|
||
--green: #609060; --green-dim: #305030; --orange: #c08050; --blue: #6088b0;
|
||
--border: #383028; --border-light: #484030;
|
||
--shadow: 0 4px 24px rgba(0,0,0,0.6); --shadow-lg: 0 12px 48px rgba(0,0,0,0.7); --shadow-card: 0 2px 12px rgba(0,0,0,0.5);
|
||
--editor-bg: #1c1816; --editor-text: #e8e0d8;
|
||
--radius-sm: 6px; --radius: 10px; --radius-lg: 14px; --radius-xl: 18px;
|
||
--font-mono: 'SF Mono','Cascadia Code','JetBrains Mono','Fira Code',monospace;
|
||
--font-sans: -apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;
|
||
--transition: 0.2s cubic-bezier(0.4,0,0.2,1);
|
||
--transition-slow: 0.35s cubic-bezier(0.4,0,0.2,1);
|
||
}
|
||
/* 竹语 — 竹林雨后 · 深绿调 */
|
||
[data-theme="bamboo"] { --bg-primary:#1a1e18;--bg-secondary:#1e221c;--bg-tertiary:#242820;--bg-surface:#282c24;--bg-hover:#2e322a;--bg-active:#343830;--bg-card:#20241c;--text-primary:#e0e4d8;--text-secondary:#b0b8a8;--text-muted:#687858;--text-dim:#485838;--accent:#7a9a60;--accent-glow:rgba(122,154,96,0.25);--accent-light:#98b878;--accent-dark:#5a7a40;--gold:#b0a048;--gold-dim:#706028;--red:#b05850;--red-dim:#703030;--green:#7a9a60;--green-dim:#3a5030;--orange:#b88050;--blue:#6088a8;--border:#303828;--border-light:#404838;--shadow:0 4px 24px rgba(0,0,0,0.6);--shadow-lg:0 12px 48px rgba(0,0,0,0.7);--shadow-card:0 2px 12px rgba(0,0,0,0.5);--editor-bg:#1a1e18;--editor-text:#e0e4d8; }
|
||
/* 月夜 — 月光静谧 · 深冷调 */
|
||
[data-theme="moonlit"] { --bg-primary:#1a1e24;--bg-secondary:#1e2228;--bg-tertiary:#242830;--bg-surface:#282c34;--bg-hover:#2e3238;--bg-active:#343840;--bg-card:#202428;--text-primary:#d8dce4;--text-secondary:#a8b0b8;--text-muted:#586878;--text-dim:#384858;--accent:#8098b8;--accent-glow:rgba(128,152,184,0.25);--accent-light:#a0b4d0;--accent-dark:#607898;--gold:#b0a860;--gold-dim:#706838;--red:#b06058;--red-dim:#703838;--green:#68a070;--green-dim:#385838;--orange:#b88858;--blue:#8098b8;--border:#2a3038;--border-light:#3a4048;--shadow:0 4px 24px rgba(0,0,0,0.6);--shadow-lg:0 12px 48px rgba(0,0,0,0.7);--shadow-card:0 2px 12px rgba(0,0,0,0.5);--editor-bg:#1a1e24;--editor-text:#d8dce4; }
|
||
/* 宣纸 — 古籍书卷 · 浅暖调 */
|
||
[data-theme="ricepaper"] { --bg-primary:#f4efe4;--bg-secondary:#faf6ee;--bg-tertiary:#efe8d8;--bg-surface:#fefaf2;--bg-hover:#e8e0d0;--bg-active:#ded6c4;--bg-card:#fcf6ec;--text-primary:#3a3028;--text-secondary:#6a5a48;--text-muted:#988878;--text-dim:#b8a898;--accent:#8b4513;--accent-glow:rgba(139,69,19,0.15);--accent-light:#a86030;--accent-dark:#6b3010;--gold:#b89840;--gold-dim:#786028;--red:#b04840;--red-dim:#783028;--green:#508850;--green-dim:#285828;--orange:#b87040;--blue:#4878a8;--border:#d8d0c0;--border-light:#e0d8c8;--shadow:0 4px 24px rgba(0,0,0,0.06);--shadow-lg:0 12px 48px rgba(0,0,0,0.1);--shadow-card:0 2px 12px rgba(0,0,0,0.04);--editor-bg:#fdf9f2;--editor-text:#3a3028; }
|
||
/* 晨雾 — 清晨薄雾 · 浅灰调 */
|
||
[data-theme="mist"] { --bg-primary:#eff0f2;--bg-secondary:#f5f6f8;--bg-tertiary:#eaeaee;--bg-surface:#fafafc;--bg-hover:#e2e3e8;--bg-active:#d8dae0;--bg-card:#f8f8fa;--text-primary:#2c3038;--text-secondary:#585c68;--text-muted:#888c98;--text-dim:#b0b4b8;--accent:#6878a0;--accent-glow:rgba(104,120,160,0.15);--accent-light:#8898b8;--accent-dark:#485880;--gold:#b0a058;--gold-dim:#706838;--red:#b05850;--red-dim:#783838;--green:#589068;--green-dim:#305838;--orange:#b87850;--blue:#6878a0;--border:#d8dae0;--border-light:#e2e4e8;--shadow:0 4px 24px rgba(0,0,0,0.05);--shadow-lg:0 12px 48px rgba(0,0,0,0.08);--shadow-card:0 2px 12px rgba(0,0,0,0.03);--editor-bg:#f8f9fc;--editor-text:#2c3038; }
|
||
/* 茶园 — 茶园新叶 · 浅绿调 */
|
||
[data-theme="teagarden"] { --bg-primary:#eef0e4;--bg-secondary:#f4f6ec;--bg-tertiary:#e6e8d8;--bg-surface:#fafcf2;--bg-hover:#dee2d0;--bg-active:#d4d8c4;--bg-card:#f2f4e8;--text-primary:#2d3a28;--text-secondary:#4a5840;--text-muted:#7a8870;--text-dim:#a0b098;--accent:#688848;--accent-glow:rgba(104,136,72,0.15);--accent-light:#80a060;--accent-dark:#486830;--gold:#a89840;--gold-dim:#686028;--red:#a85048;--red-dim:#683028;--green:#688848;--green-dim:#304828;--orange:#b07848;--blue:#4878a0;--border:#d0d8c0;--border-light:#dce4cc;--shadow:0 4px 24px rgba(0,0,0,0.05);--shadow-lg:0 12px 48px rgba(0,0,0,0.08);--shadow-card:0 2px 12px rgba(0,0,0,0.03);--editor-bg:#f6faf0;--editor-text:#2d3a28; }
|
||
/* 暮光 — 落日余晖 · 深暖调 */
|
||
[data-theme="twilight"] { --bg-primary:#1f1a16;--bg-secondary:#241e1a;--bg-tertiary:#2a2420;--bg-surface:#2e2824;--bg-hover:#342e28;--bg-active:#3a342e;--bg-card:#26201c;--text-primary:#e8dcc8;--text-secondary:#b8a890;--text-muted:#786850;--text-dim:#584838;--accent:#c88850;--accent-glow:rgba(200,136,80,0.25);--accent-light:#d8a870;--accent-dark:#a06838;--gold:#c89848;--gold-dim:#786028;--red:#c05848;--red-dim:#783030;--green:#609058;--green-dim:#305028;--orange:#c88850;--blue:#5880a8;--border:#383028;--border-light:#484038;--shadow:0 4px 24px rgba(0,0,0,0.6);--shadow-lg:0 12px 48px rgba(0,0,0,0.7);--shadow-card:0 2px 12px rgba(0,0,0,0.5);--editor-bg:#1f1a16;--editor-text:#e8dcc8; }
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
html,
|
||
body {
|
||
height: 100%;
|
||
overflow: hidden;
|
||
font-family: var(--font-sans);
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
user-select: none;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
#app {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
width: 100vw;
|
||
}
|
||
|
||
/* ============ TOPBAR ============ */
|
||
#topbar {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 44px;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
padding: 0 10px;
|
||
gap: 2px;
|
||
-webkit-app-region: drag;
|
||
z-index: 100;
|
||
}
|
||
#topbar .logo-area {
|
||
-webkit-app-region: no-drag;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
letter-spacing: 0.5px;
|
||
cursor: pointer;
|
||
padding: 6px 10px;
|
||
border-radius: var(--radius-sm);
|
||
transition: var(--transition);
|
||
margin-right: 6px;
|
||
white-space: nowrap;
|
||
}
|
||
#topbar .logo-area:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
#topbar .tab {
|
||
-webkit-app-region: no-drag;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
padding: 7px 14px;
|
||
border-radius: var(--radius-sm);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
transition: var(--transition);
|
||
white-space: nowrap;
|
||
font-weight: 500;
|
||
position: relative;
|
||
max-width: 180px;
|
||
}
|
||
#topbar .tab .tab-label {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
#topbar .tab.active {
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
border-color: var(--border-light);
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||
}
|
||
#topbar .tab:hover:not(.active) {
|
||
background: var(--bg-hover);
|
||
color: var(--text-primary);
|
||
}
|
||
#topbar .tab .dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
background: var(--accent);
|
||
flex-shrink: 0;
|
||
}
|
||
#topbar .tab .close-tab {
|
||
opacity: 0;
|
||
margin-left: 2px;
|
||
font-size: 10px;
|
||
transition: var(--transition);
|
||
color: var(--text-muted);
|
||
flex-shrink: 0;
|
||
}
|
||
#topbar .tab:hover .close-tab {
|
||
opacity: 1;
|
||
}
|
||
#topbar .tab .close-tab:hover {
|
||
color: var(--red);
|
||
}
|
||
#topbar .spacer {
|
||
flex: 1;
|
||
-webkit-app-region: drag;
|
||
}
|
||
#topbar .top-btn {
|
||
-webkit-app-region: no-drag;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: var(--radius-sm);
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
font-size: 15px;
|
||
transition: var(--transition);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
#topbar .top-btn:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
#topbar .top-btn.accent {
|
||
color: var(--accent-light);
|
||
}
|
||
/* 窗口控制按钮 */
|
||
.window-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
.window-close-btn:hover {
|
||
background: var(--red) !important;
|
||
color: #fff !important;
|
||
}
|
||
#btn-maximize .fa-window-maximize {
|
||
font-size: 11px;
|
||
}
|
||
/* ============ MAIN AREA ============ */
|
||
#main-area {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ============ HOME PAGE ============ */
|
||
#home-page {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
background: var(--bg-primary);
|
||
}
|
||
#home-page.hidden {
|
||
display: none;
|
||
}
|
||
#home-page::-webkit-scrollbar {
|
||
width: 5px;
|
||
}
|
||
#home-page::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
}
|
||
.home-hero {
|
||
padding: 40px 48px 28px;
|
||
text-align: center;
|
||
}
|
||
.home-hero h1 {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
letter-spacing: 1px;
|
||
}
|
||
.home-hero .subtitle {
|
||
font-size: 13px;
|
||
color: var(--text-muted);
|
||
margin-top: 6px;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.home-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
padding: 0 48px 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.home-actions .btn-large {
|
||
padding: 12px 28px;
|
||
border-radius: var(--radius);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
transition: var(--transition);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.home-actions .btn-large.primary {
|
||
background: var(--accent);
|
||
border-color: var(--accent);
|
||
color: #fff;
|
||
}
|
||
.home-actions .btn-large.primary:hover {
|
||
background: var(--accent-light);
|
||
box-shadow: 0 0 20px var(--accent-glow);
|
||
}
|
||
.home-actions .btn-large:hover {
|
||
background: var(--bg-hover);
|
||
border-color: var(--border-light);
|
||
}
|
||
.home-section {
|
||
padding: 16px 48px 24px;
|
||
}
|
||
.home-section .section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 14px;
|
||
}
|
||
.home-section .section-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: var(--text-secondary);
|
||
letter-spacing: 0.5px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.home-section .section-count {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
font-weight: 400;
|
||
}
|
||
.book-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 14px;
|
||
}
|
||
.book-card {
|
||
background: var(--bg-card);
|
||
border-radius: var(--radius-lg);
|
||
border: 1px solid var(--border);
|
||
padding: 20px;
|
||
cursor: pointer;
|
||
transition: var(--transition);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.book-card:hover {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--accent-glow);
|
||
transform: translateY(-2px);
|
||
}
|
||
.book-card .book-cover-icon {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: var(--radius);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 22px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.book-card .book-name {
|
||
font-weight: 700;
|
||
font-size: 15px;
|
||
color: #fff;
|
||
margin-bottom: 4px;
|
||
}
|
||
.book-card .book-meta {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
line-height: 1.6;
|
||
}
|
||
.book-card .book-meta span {
|
||
margin-right: 10px;
|
||
}
|
||
.book-card .book-progress {
|
||
margin-top: 10px;
|
||
height: 3px;
|
||
background: var(--border);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
.book-card .book-progress-fill {
|
||
height: 100%;
|
||
border-radius: 2px;
|
||
background: var(--accent);
|
||
}
|
||
.book-card .book-badge {
|
||
position: absolute;
|
||
top: 14px;
|
||
right: 14px;
|
||
font-size: 9px;
|
||
padding: 3px 8px;
|
||
border-radius: 10px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.badge-draft {
|
||
background: var(--orange);
|
||
color: #000;
|
||
}
|
||
.badge-ongoing {
|
||
background: var(--accent);
|
||
color: #fff;
|
||
}
|
||
.badge-done {
|
||
background: var(--green);
|
||
color: #000;
|
||
}
|
||
.book-card-placeholder {
|
||
background: transparent;
|
||
border: 2px dashed var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 140px;
|
||
color: var(--text-muted);
|
||
font-size: 13px;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
transition: var(--transition);
|
||
}
|
||
.book-card-placeholder:hover {
|
||
border-color: var(--accent);
|
||
color: var(--accent-light);
|
||
background: rgba(124, 140, 248, 0.03);
|
||
}
|
||
.book-card-placeholder .plus-icon {
|
||
font-size: 32px;
|
||
color: var(--text-dim);
|
||
transition: var(--transition);
|
||
}
|
||
.book-card-placeholder:hover .plus-icon {
|
||
color: var(--accent-light);
|
||
}
|
||
|
||
/* ============ EDITOR LAYOUT ============ */
|
||
#editor-layout {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
#editor-layout.hidden {
|
||
display: none;
|
||
}
|
||
|
||
/* ============ LEFT SIDEBAR ============ */
|
||
#left-sidebar {
|
||
width: 280px;
|
||
min-width: 220px;
|
||
background: var(--bg-secondary);
|
||
border-right: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
overflow: hidden;
|
||
resize: horizontal;
|
||
}
|
||
#left-sidebar .sidebar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 14px;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
gap: 8px;
|
||
}
|
||
#left-sidebar .sidebar-header .book-title {
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
color: #fff;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
flex: 1;
|
||
}
|
||
#left-sidebar .sidebar-header .badge {
|
||
font-size: 10px;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.5px;
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-nav {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-nav .nav-btn {
|
||
flex: 1;
|
||
padding: 9px 4px;
|
||
text-align: center;
|
||
font-size: 10.5px;
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
border: none;
|
||
background: transparent;
|
||
border-bottom: 2px solid transparent;
|
||
transition: var(--transition);
|
||
font-weight: 500;
|
||
letter-spacing: 0.3px;
|
||
white-space: nowrap;
|
||
}
|
||
.sidebar-nav .nav-btn.active {
|
||
color: var(--accent-light);
|
||
border-bottom-color: var(--accent);
|
||
background: rgba(124, 140, 248, 0.05);
|
||
}
|
||
.sidebar-nav .nav-btn:hover {
|
||
color: var(--text-primary);
|
||
background: var(--bg-hover);
|
||
}
|
||
.sidebar-panel {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
display: none;
|
||
flex-direction: column;
|
||
}
|
||
.sidebar-panel.active {
|
||
display: flex;
|
||
}
|
||
.sidebar-panel::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
.sidebar-panel::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* Chapter tree */
|
||
#chapter-tree {
|
||
padding: 6px 4px;
|
||
scroll-behavior: smooth;
|
||
}
|
||
.vol-group {
|
||
margin-bottom: 4px;
|
||
}
|
||
.vol-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 7px 10px;
|
||
cursor: pointer;
|
||
border-radius: var(--radius-sm);
|
||
transition: var(--transition);
|
||
gap: 6px;
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
letter-spacing: 0.3px;
|
||
}
|
||
.vol-header:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
.vol-header .vol-arrow {
|
||
font-size: 9px;
|
||
transition: var(--transition);
|
||
color: var(--text-muted);
|
||
}
|
||
.vol-header.open .vol-arrow {
|
||
transform: rotate(90deg);
|
||
}
|
||
.vol-header .vol-count {
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
margin-left: auto;
|
||
}
|
||
.chapter-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 6px 10px 6px 28px;
|
||
cursor: pointer;
|
||
border-radius: var(--radius-sm);
|
||
transition: var(--transition);
|
||
gap: 8px;
|
||
font-size: 12.5px;
|
||
color: var(--text-secondary);
|
||
position: relative;
|
||
margin: 1px 0;
|
||
}
|
||
.chapter-item:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
.chapter-item.active {
|
||
background: var(--bg-active);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
box-shadow: inset 3px 0 0 var(--accent);
|
||
}
|
||
.chapter-item .ch-num {
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
font-family: var(--font-mono);
|
||
min-width: 28px;
|
||
}
|
||
.chapter-item .ch-title {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.chapter-item .ch-badge {
|
||
font-size: 9px;
|
||
padding: 2px 6px;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.5px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ch-badge.draft {
|
||
background: var(--orange);
|
||
color: #000;
|
||
}
|
||
.ch-badge.ai {
|
||
background: var(--accent);
|
||
color: #fff;
|
||
}
|
||
.ch-badge.review {
|
||
background: var(--gold);
|
||
color: #000;
|
||
}
|
||
.ch-badge.done {
|
||
background: var(--green);
|
||
color: #000;
|
||
}
|
||
#left-sidebar .sidebar-footer {
|
||
padding: 8px 12px;
|
||
border-top: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
#left-sidebar .sidebar-footer button {
|
||
flex: 1;
|
||
padding: 7px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border);
|
||
background: transparent;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
font-size: 11px;
|
||
transition: var(--transition);
|
||
font-weight: 500;
|
||
}
|
||
#left-sidebar .sidebar-footer button:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
border-color: var(--border-light);
|
||
}
|
||
|
||
/* Settings panel in sidebar - 书籍设定 */
|
||
.setting-cat {
|
||
padding: 8px 12px;
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
text-transform: uppercase;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.setting-entry {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
border-radius: var(--radius-sm);
|
||
transition: var(--transition);
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
margin: 1px 4px;
|
||
}
|
||
.setting-entry:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
.setting-entry .entry-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 11px;
|
||
flex-shrink: 0;
|
||
}
|
||
.entry-icon.char {
|
||
background: rgba(124, 140, 248, 0.2);
|
||
color: var(--accent-light);
|
||
}
|
||
.entry-icon.skill {
|
||
background: rgba(240, 192, 96, 0.2);
|
||
color: var(--gold);
|
||
}
|
||
.entry-icon.world {
|
||
background: rgba(92, 184, 120, 0.2);
|
||
color: var(--green);
|
||
}
|
||
.entry-icon.scene {
|
||
background: rgba(91, 156, 245, 0.2);
|
||
color: var(--blue);
|
||
}
|
||
.setting-entry .entry-name {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.setting-entry .entry-count {
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* ============ CENTER EDITOR ============ */
|
||
#editor-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: var(--bg-primary);
|
||
overflow: hidden;
|
||
min-width: 300px;
|
||
}
|
||
#editor-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
padding: 7px 14px;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
background: var(--bg-secondary);
|
||
}
|
||
#editor-toolbar .tool-btn {
|
||
width: 30px;
|
||
height: 28px;
|
||
border-radius: 4px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-secondary);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
transition: var(--transition);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
#editor-toolbar .tool-btn:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
#editor-toolbar .sep {
|
||
width: 1px;
|
||
height: 20px;
|
||
background: var(--border);
|
||
margin: 0 6px;
|
||
}
|
||
#editor-toolbar .ch-label {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-left: 8px;
|
||
font-weight: 500;
|
||
}
|
||
#editor-toolbar .word-count-display {
|
||
font-size: 11px;
|
||
color: var(--accent-light);
|
||
margin-left: auto;
|
||
font-family: var(--font-mono);
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
#editor-toolbar .word-count-display.warn {
|
||
color: var(--orange);
|
||
}
|
||
#editor-content {
|
||
flex: 1;
|
||
padding: 32px 48px;
|
||
overflow-y: auto;
|
||
line-height: 2;
|
||
font-size: 16px;
|
||
color: #d8d8e0;
|
||
outline: none;
|
||
font-family: 'Georgia', 'Noto Serif SC', 'Source Han Serif SC', 'STSong', serif;
|
||
letter-spacing: 0.4px;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
scroll-behavior: smooth;
|
||
}
|
||
#editor-content:focus {
|
||
color: #eaeaef;
|
||
}
|
||
#editor-content::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
#editor-content::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
}
|
||
#editor-content .placeholder {
|
||
color: var(--text-dim);
|
||
pointer-events: none;
|
||
}
|
||
#editor-statusbar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 5px 16px;
|
||
border-top: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
gap: 16px;
|
||
background: var(--bg-secondary);
|
||
}
|
||
#editor-statusbar span {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
#editor-statusbar .speed-indicator {
|
||
color: var(--accent-light);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ============ RIGHT PANEL ============ */
|
||
#right-panel {
|
||
width: 340px;
|
||
min-width: 280px;
|
||
background: var(--bg-secondary);
|
||
border-left: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-shrink: 0;
|
||
overflow: hidden;
|
||
resize: horizontal;
|
||
}
|
||
#right-panel .panel-tabs {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
#right-panel .panel-tab {
|
||
flex: 1;
|
||
padding: 10px 6px;
|
||
text-align: center;
|
||
font-size: 10.5px;
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
border: none;
|
||
background: transparent;
|
||
border-bottom: 2px solid transparent;
|
||
transition: var(--transition);
|
||
font-weight: 500;
|
||
letter-spacing: 0.3px;
|
||
white-space: nowrap;
|
||
}
|
||
#right-panel .panel-tab.active {
|
||
color: var(--accent-light);
|
||
border-bottom-color: var(--accent);
|
||
}
|
||
#right-panel .panel-tab:hover {
|
||
color: var(--text-primary);
|
||
background: var(--bg-hover);
|
||
}
|
||
#right-panel .panel-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
display: none;
|
||
flex-direction: column;
|
||
}
|
||
#right-panel .panel-content.active {
|
||
display: flex;
|
||
}
|
||
#right-panel .panel-content::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
#right-panel .panel-content::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* AI Chat */
|
||
.chat-messages {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.chat-msg {
|
||
padding: 10px 12px;
|
||
border-radius: var(--radius);
|
||
font-size: 12.5px;
|
||
line-height: 1.6;
|
||
max-width: 92%;
|
||
animation: fadeInUp 0.25s ease;
|
||
}
|
||
@keyframes fadeInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(8px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
.chat-msg.user {
|
||
align-self: flex-end;
|
||
background: var(--accent-dark);
|
||
color: #fff;
|
||
border-bottom-right-radius: 4px;
|
||
}
|
||
.chat-msg.ai {
|
||
align-self: flex-start;
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-primary);
|
||
border-bottom-left-radius: 4px;
|
||
border: 1px solid var(--border-light);
|
||
}
|
||
.chat-msg.ai .plot-options {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.chat-msg.ai .plot-opt {
|
||
padding: 8px 10px;
|
||
border-radius: var(--radius-sm);
|
||
background: var(--bg-surface);
|
||
cursor: pointer;
|
||
border: 1px solid var(--border);
|
||
transition: var(--transition);
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
}
|
||
.chat-msg.ai .plot-opt:hover {
|
||
border-color: var(--accent);
|
||
color: #fff;
|
||
background: var(--bg-hover);
|
||
}
|
||
.chat-msg.ai .plot-opt .opt-label {
|
||
font-weight: 700;
|
||
color: var(--accent-light);
|
||
font-size: 10px;
|
||
}
|
||
.chat-input-area {
|
||
padding: 10px;
|
||
border-top: 1px solid var(--border);
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
.chat-input-area input {
|
||
flex: 1;
|
||
padding: 9px 12px;
|
||
border-radius: var(--radius);
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
outline: none;
|
||
font-family: var(--font-sans);
|
||
}
|
||
.chat-input-area input:focus {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px var(--accent-glow);
|
||
}
|
||
.chat-input-area button {
|
||
padding: 9px 14px;
|
||
border-radius: var(--radius);
|
||
border: none;
|
||
background: var(--accent);
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
transition: var(--transition);
|
||
}
|
||
.chat-input-area button:hover {
|
||
background: var(--accent-light);
|
||
}
|
||
.quick-cmds {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 6px 10px;
|
||
flex-wrap: wrap;
|
||
flex-shrink: 0;
|
||
border-top: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
}
|
||
.quick-cmd {
|
||
font-size: 10px;
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
background: var(--bg-surface);
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
transition: var(--transition);
|
||
white-space: nowrap;
|
||
}
|
||
.quick-cmd:hover {
|
||
border-color: var(--accent);
|
||
color: var(--accent-light);
|
||
}
|
||
|
||
/* Settings panel (右侧书籍设置) */
|
||
.settings-group {
|
||
padding: 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.settings-group h4 {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 8px;
|
||
letter-spacing: 1px;
|
||
text-transform: uppercase;
|
||
}
|
||
.settings-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 6px 0;
|
||
font-size: 12px;
|
||
gap: 8px;
|
||
}
|
||
.settings-row input[type="range"] {
|
||
flex: 1;
|
||
accent-color: var(--accent);
|
||
}
|
||
.settings-row input[type="number"] {
|
||
width: 50px;
|
||
padding: 4px 6px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
font-size: 11px;
|
||
text-align: center;
|
||
}
|
||
.settings-row select {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
font-size: 11px;
|
||
}
|
||
|
||
/* Knowledge panel */
|
||
.kb-item {
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: pointer;
|
||
transition: var(--transition);
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.kb-item:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
.kb-item .kb-stars {
|
||
color: var(--gold);
|
||
font-size: 10px;
|
||
}
|
||
.kb-item .kb-tag {
|
||
font-size: 9px;
|
||
padding: 2px 6px;
|
||
border-radius: 8px;
|
||
background: var(--bg-active);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* ============ SETTINGS PAGE (as tab) ============ */
|
||
#settings-page {
|
||
flex: 1;
|
||
display: flex;
|
||
overflow-y: auto;
|
||
background: var(--bg-primary);
|
||
}
|
||
#settings-page.hidden {
|
||
display: none;
|
||
}
|
||
#settings-page::-webkit-scrollbar {
|
||
width: 5px;
|
||
}
|
||
#settings-page::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
}
|
||
.settings-layout {
|
||
display: flex;
|
||
flex: 1;
|
||
}
|
||
.settings-sidebar {
|
||
width: 200px;
|
||
background: var(--bg-secondary);
|
||
border-right: 1px solid var(--border);
|
||
padding: 8px 0;
|
||
flex-shrink: 0;
|
||
}
|
||
.settings-sidebar .settings-nav-item {
|
||
padding: 10px 16px;
|
||
cursor: pointer;
|
||
font-size: 12.5px;
|
||
color: var(--text-secondary);
|
||
transition: var(--transition);
|
||
border-left: 3px solid transparent;
|
||
font-weight: 500;
|
||
}
|
||
.settings-sidebar .settings-nav-item:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
.settings-sidebar .settings-nav-item.active {
|
||
color: #fff;
|
||
background: var(--bg-tertiary);
|
||
border-left-color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
.settings-content {
|
||
flex: 1;
|
||
padding: 24px 32px;
|
||
overflow-y: auto;
|
||
}
|
||
.settings-content::-webkit-scrollbar {
|
||
width: 5px;
|
||
}
|
||
.settings-content::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
}
|
||
.settings-content h2 {
|
||
font-size: 20px;
|
||
color: #fff;
|
||
margin-bottom: 20px;
|
||
font-weight: 700;
|
||
}
|
||
.settings-content .setting-block {
|
||
margin-bottom: 24px;
|
||
}
|
||
.settings-content .setting-block h4 {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
letter-spacing: 1px;
|
||
margin-bottom: 10px;
|
||
text-transform: uppercase;
|
||
}
|
||
.settings-content .setting-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 14px;
|
||
background: var(--bg-tertiary);
|
||
border-radius: var(--radius-sm);
|
||
margin-bottom: 6px;
|
||
font-size: 13px;
|
||
gap: 12px;
|
||
}
|
||
.settings-content .setting-item .setting-label {
|
||
flex: 1;
|
||
color: var(--text-primary);
|
||
}
|
||
.settings-content .setting-item .setting-desc {
|
||
font-size: 10px;
|
||
color: var(--text-muted);
|
||
}
|
||
.settings-content input[type="text"],
|
||
.settings-content input[type="number"],
|
||
.settings-content select {
|
||
padding: 6px 10px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-surface);
|
||
color: #fff;
|
||
font-size: 12px;
|
||
font-family: var(--font-sans);
|
||
}
|
||
|
||
/* ============ MODALS ============ */
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
animation: fadeIn 0.2s ease;
|
||
}
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
.modal {
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--radius-lg);
|
||
border: 1px solid var(--border);
|
||
box-shadow: var(--shadow-lg);
|
||
width: 90%;
|
||
max-width: 800px;
|
||
max-height: 85vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
animation: scaleIn 0.25s ease;
|
||
}
|
||
@keyframes scaleIn {
|
||
from {
|
||
transform: scale(0.95);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
.modal-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 18px;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.modal-header h3 {
|
||
font-size: 15px;
|
||
color: #fff;
|
||
}
|
||
.modal-close {
|
||
width: 30px;
|
||
height: 30px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
transition: var(--transition);
|
||
}
|
||
.modal-close:hover {
|
||
background: var(--bg-hover);
|
||
color: #fff;
|
||
}
|
||
.modal-body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 18px;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
.modal-body::-webkit-scrollbar {
|
||
width: 5px;
|
||
}
|
||
.modal-body::-webkit-scrollbar-thumb {
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
}
|
||
.modal-footer {
|
||
padding: 12px 18px;
|
||
border-top: 1px solid var(--border);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.btn {
|
||
padding: 8px 16px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border);
|
||
background: transparent;
|
||
color: var(--text-primary);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
transition: var(--transition);
|
||
}
|
||
.btn:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
.btn.primary {
|
||
background: var(--accent);
|
||
border-color: var(--accent);
|
||
color: #fff;
|
||
font-weight: 600;
|
||
}
|
||
.btn.primary:hover {
|
||
background: var(--accent-light);
|
||
}
|
||
.btn.danger {
|
||
border-color: var(--red-dim);
|
||
color: var(--red);
|
||
}
|
||
.btn.danger:hover {
|
||
background: var(--red);
|
||
color: #fff;
|
||
border-color: var(--red);
|
||
}
|
||
.hidden {
|
||
display: none !important;
|
||
}
|
||
.diff-view {
|
||
display: flex;
|
||
gap: 2px;
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
.diff-pane {
|
||
flex: 1;
|
||
padding: 10px;
|
||
background: var(--bg-tertiary);
|
||
border-radius: var(--radius-sm);
|
||
overflow-y: auto;
|
||
max-height: 400px;
|
||
white-space: pre-wrap;
|
||
}
|
||
.diff-pane .diff-add {
|
||
background: rgba(92, 184, 120, 0.25);
|
||
color: var(--green);
|
||
}
|
||
.diff-pane .diff-del {
|
||
background: rgba(224, 85, 106, 0.25);
|
||
color: var(--red);
|
||
text-decoration: line-through;
|
||
}
|
||
.naming-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.naming-opt {
|
||
padding: 10px 14px;
|
||
border-radius: var(--radius);
|
||
background: var(--bg-tertiary);
|
||
cursor: pointer;
|
||
border: 1px solid var(--border);
|
||
transition: var(--transition);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.naming-opt:hover {
|
||
border-color: var(--accent);
|
||
background: var(--bg-hover);
|
||
}
|
||
.naming-opt .name-cn {
|
||
font-weight: 700;
|
||
font-size: 15px;
|
||
color: #fff;
|
||
}
|
||
.naming-opt .name-desc {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
}
|
||
#focus-indicator {
|
||
display: none;
|
||
position: fixed;
|
||
top: 12px;
|
||
right: 20px;
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
z-index: 999;
|
||
letter-spacing: 1px;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%,
|
||
100% {
|
||
opacity: 0.4;
|
||
}
|
||
50% {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
#app.focus-mode #topbar,
|
||
#app.focus-mode #left-sidebar,
|
||
#app.focus-mode #right-panel,
|
||
#app.focus-mode #editor-toolbar,
|
||
#app.focus-mode #editor-statusbar {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
#app.focus-mode #editor-area {
|
||
flex: 1;
|
||
}
|
||
#app.focus-mode #editor-content {
|
||
padding: 60px 120px;
|
||
font-size: 18px;
|
||
line-height: 2.2;
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
}
|
||
#app.focus-mode #editor-content::before {
|
||
content: '';
|
||
display: block;
|
||
height: 30vh;
|
||
}
|
||
#app.focus-mode #focus-indicator {
|
||
display: block;
|
||
}
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 30px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: var(--bg-surface);
|
||
color: #fff;
|
||
padding: 10px 20px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
z-index: 2000;
|
||
box-shadow: var(--shadow-lg);
|
||
border: 1px solid var(--border-light);
|
||
animation: toastIn 0.3s ease, toastOut 0.3s ease 2s forwards;
|
||
pointer-events: none;
|
||
}
|
||
@keyframes toastIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(-50%) translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(-50%) translateY(0);
|
||
}
|
||
}
|
||
@keyframes toastOut {
|
||
from {
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
opacity: 0;
|
||
transform: translateX(-50%) translateY(-10px);
|
||
}
|
||
}
|
||
.dashboard-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.dash-card {
|
||
background: var(--bg-tertiary);
|
||
border-radius: var(--radius);
|
||
padding: 16px;
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
transition: var(--transition);
|
||
}
|
||
.dash-card:hover {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 16px var(--accent-glow);
|
||
}
|
||
.dash-card .dash-num {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
}
|
||
.dash-card .dash-label {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-top: 4px;
|
||
}
|
||
.heatmap-cell {
|
||
display: inline-block;
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 3px;
|
||
margin: 1px;
|
||
background: var(--bg-active);
|
||
}
|
||
.heatmap-cell.filled {
|
||
background: var(--accent);
|
||
}
|
||
.heatmap-cell.high {
|
||
background: var(--accent-light);
|
||
}
|
||
@media (max-width: 1100px) {
|
||
#right-panel {
|
||
width: 260px;
|
||
min-width: 200px;
|
||
}
|
||
#left-sidebar {
|
||
width: 220px;
|
||
min-width: 180px;
|
||
}
|
||
#editor-content {
|
||
padding: 20px 24px;
|
||
}
|
||
.book-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
}
|
||
}
|
||
|
||
/* 新建书籍弹窗 */
|
||
.form-group {
|
||
margin-bottom: 12px;
|
||
}
|
||
.form-group label {
|
||
display: block;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 4px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.form-group input,
|
||
.form-group select {
|
||
width: 100%;
|
||
padding: 9px 12px;
|
||
border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border);
|
||
background: var(--bg-tertiary);
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-family: var(--font-sans);
|
||
outline: none;
|
||
}
|
||
.form-group input:focus,
|
||
.form-group select:focus {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px var(--accent-glow);
|
||
}
|
||
|
||
/* ============ NEW COMPONENT STYLES (V2.0) ============ */
|
||
#dailyGoalBar { -webkit-app-region:no-drag;display:flex;align-items:center;gap:6px;padding:0 8px;cursor:pointer; }
|
||
#dailyGoalBar .goal-track { width:80px;height:5px;background:var(--bg-active);border-radius:3px;overflow:hidden; }
|
||
#dailyGoalBar .goal-fill { height:100%;border-radius:3px;background:var(--accent);transition:width 0.5s ease; }
|
||
#dailyGoalBar .goal-fill.warn { background:var(--orange); } #dailyGoalBar .goal-fill.done { background:var(--green); }
|
||
#dailyGoalBar .goal-text { font-size:10px;color:var(--text-muted);white-space:nowrap; }
|
||
#dailyGoalBar:hover .goal-text { color:var(--text-primary); }
|
||
.sound-popover { display:none;position:absolute;top:38px;right:0;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-lg);z-index:200;min-width:180px;padding:6px 0; }
|
||
.sound-popover.show { display:block; }
|
||
.sound-popover .sound-item { padding:8px 14px;cursor:pointer;font-size:12px;color:var(--text-secondary);transition:var(--transition);display:flex;align-items:center;gap:8px;white-space:nowrap; }
|
||
.sound-popover .sound-item:hover { background:var(--bg-hover);color:#fff; }
|
||
.sound-popover .sound-item.active { color:var(--accent-light);font-weight:600; }
|
||
.sound-popover .sound-item .vol-slider { width:60px;margin-left:auto;accent-color:var(--accent); }
|
||
.sound-popover .sound-divider { height:1px;background:var(--border);margin:4px 0; }
|
||
.chapter-item .ch-select { display:none;width:16px;height:16px;border-radius:3px;border:2px solid var(--text-muted);flex-shrink:0;cursor:pointer;align-items:center;justify-content:center;font-size:9px;color:transparent;position:absolute;left:6px; }
|
||
.chapter-item:hover .ch-select { display:flex; }
|
||
.chapter-item .ch-select.checked, .chapter-item.selected .ch-select { display:flex;background:var(--accent);border-color:var(--accent);color:#fff; }
|
||
.chapter-item.selected { background:var(--bg-hover); }
|
||
#batchToolbar { display:none;align-items:center;gap:6px;padding:6px 10px;background:var(--accent-dark);border-radius:var(--radius-sm);margin:4px 8px;font-size:11px;color:#fff;flex-shrink:0; }
|
||
#batchToolbar.show { display:flex; }
|
||
#batchToolbar button { padding:3px 8px;border-radius:3px;border:1px solid rgba(255,255,255,0.2);background:transparent;color:#fff;cursor:pointer;font-size:10px;transition:var(--transition); }
|
||
#batchToolbar button:hover { background:rgba(255,255,255,0.15); }
|
||
.outline-item { display:flex;align-items:center;padding:6px 8px 6px 18px;cursor:pointer;border-radius:4px;font-size:12px;color:var(--text-secondary);border-left:3px solid transparent;margin:1px 4px;gap:6px;transition:var(--transition); }
|
||
.outline-item:hover { background:var(--bg-hover);color:#fff; }
|
||
.outline-item.uncovered { border-left-color:var(--orange); }
|
||
.outline-item.uncovered::after { content:'\26A0';font-size:10px;color:var(--orange);margin-left:auto; }
|
||
#editor-toolbar .tool-btn.active { color:var(--accent-light);background:var(--bg-active); }
|
||
#editor-toolbar .tool-btn.tts-playing { color:var(--green);animation:pulse-tts 1.5s infinite; }
|
||
@keyframes pulse-tts { 0%,100%{opacity:1;} 50%{opacity:0.5;} }
|
||
#editor-content { background:var(--editor-bg);color:var(--editor-text); }
|
||
.pomodoro { display:flex;align-items:center;gap:6px;cursor:pointer;padding:2px 8px;border-radius:10px;border:1px solid var(--border);font-family:var(--font-mono);font-size:11px;transition:var(--transition); }
|
||
.pomodoro:hover { border-color:var(--accent); } .pomodoro.running { border-color:var(--red);color:var(--red);animation:pulse-tts 2s infinite; }
|
||
.wf-header { padding:10px 12px;border-bottom:1px solid var(--border);display:flex;gap:6px;flex-shrink:0; }
|
||
.wf-header button { padding:4px 10px;border-radius:10px;border:1px solid var(--border);background:transparent;color:var(--text-muted);cursor:pointer;font-size:10px;transition:var(--transition); }
|
||
.wf-header button.active { background:var(--accent);color:#fff;border-color:var(--accent); }
|
||
.wf-row { display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-radius:4px;font-size:12px;color:var(--text-secondary);gap:8px;transition:var(--transition); }
|
||
.wf-row:hover { background:var(--bg-hover); }
|
||
.wf-row .wf-rank { font-size:10px;color:var(--text-muted);width:20px; }
|
||
.wf-row .wf-word { flex:1;font-weight:500; } .wf-row .wf-count { font-family:var(--font-mono);font-size:11px;color:var(--accent-light); }
|
||
.wf-row .wf-bar { width:60px;height:4px;background:var(--bg-active);border-radius:2px;overflow:hidden; }
|
||
.wf-row .wf-bar-fill { height:100%;border-radius:2px;background:var(--accent); }
|
||
.wf-row.overused .wf-word { color:var(--orange); }
|
||
.offline-banner { display:none;padding:6px 12px;background:var(--orange);color:#000;font-size:11px;text-align:center;font-weight:600; }
|
||
.offline-banner.show { display:block; }
|
||
#reading-mode { display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:var(--editor-bg);z-index:500;flex-direction:column; }
|
||
#reading-mode.active { display:flex; }
|
||
#reading-mode .reading-topbar { display:flex;align-items:center;padding:8px 16px;background:var(--bg-secondary);border-bottom:1px solid var(--border);gap:8px;flex-shrink:0; }
|
||
#reading-mode .reading-topbar button { padding:6px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:transparent;color:var(--text-secondary);cursor:pointer;font-size:12px;transition:var(--transition); }
|
||
#reading-mode .reading-topbar button:hover { background:var(--bg-hover);color:#fff; }
|
||
#reading-mode .reading-topbar button.active { background:var(--accent);color:#fff;border-color:var(--accent); }
|
||
#reading-mode .reading-topbar .sep { width:1px;height:20px;background:var(--border); }
|
||
#reading-mode .reading-content { flex:1;overflow-y:scroll;scroll-snap-type:y mandatory;scroll-behavior:smooth;padding:40px 0; }
|
||
#reading-mode .reading-page { scroll-snap-align:start;min-height:100%;display:flex;align-items:center;justify-content:center;padding:40px; }
|
||
#reading-mode .reading-page .page-inner { max-width:680px;width:100%;font-family:'Georgia','Noto Serif SC',serif;font-size:17px;line-height:2.2;color:var(--editor-text); }
|
||
#reading-mode.device-phone .page-inner { max-width:360px;font-size:15px; }
|
||
#reading-mode.device-tablet .page-inner { max-width:540px; }
|
||
#reading-mode.font-sans .page-inner { font-family:var(--font-sans); }
|
||
#reading-mode.font-eyecare .page-inner { background:#f5f0e0;color:#3a3028;padding:24px;border-radius:8px; }
|
||
#graphContainer { width:100%;height:60vh;background:var(--bg-primary);border-radius:var(--radius);border:1px solid var(--border); }
|
||
.graph-controls { display:flex;gap:6px;padding:8px 0;flex-wrap:wrap; }
|
||
.graph-controls button { padding:5px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:transparent;color:var(--text-secondary);cursor:pointer;font-size:11px;transition:var(--transition); }
|
||
.graph-controls button:hover,.graph-controls button.active { background:var(--accent);color:#fff;border-color:var(--accent); }
|
||
.graph-sidebar { display:none;position:absolute;right:20px;top:60px;width:240px;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px;z-index:10;font-size:12px;box-shadow:var(--shadow-lg); }
|
||
.graph-sidebar.show { display:block; }
|
||
.timeline-container { position:relative;padding:20px 20px 20px 50px; }
|
||
.timeline-container::before { content:'';position:absolute;left:30px;top:0;bottom:0;width:3px;background:var(--accent);border-radius:2px; }
|
||
.timeline-event { position:relative;margin-bottom:20px;padding:12px 16px;background:var(--bg-tertiary);border-radius:var(--radius);border:1px solid var(--border);cursor:pointer;transition:var(--transition);margin-left:20px; }
|
||
.timeline-event:hover { border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow); }
|
||
.timeline-event::before { content:'';position:absolute;left:-28px;top:18px;width:12px;height:12px;border-radius:50%;background:var(--accent);border:3px solid var(--bg-primary); }
|
||
.timeline-event .tl-time { font-size:10px;color:var(--accent-light);font-weight:600;margin-bottom:4px; }
|
||
.timeline-event .tl-title { font-weight:700;font-size:13px;color:#fff; }
|
||
.timeline-event .tl-desc { font-size:11px;color:var(--text-muted);margin-top:4px; }
|
||
.timeline-event .tl-type { font-size:9px;padding:2px 6px;border-radius:8px;background:var(--bg-active);color:var(--text-muted);display:inline-block;margin-top:4px; }
|
||
.timeline-event.conflict { border-color:var(--red); }
|
||
.timeline-event.conflict::before { background:var(--red); }
|
||
.timeline-event.conflict .tl-time { color:var(--red); }
|
||
.ctx-menu { display:none;position:fixed;background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-lg);z-index:1500;min-width:160px;padding:4px 0; }
|
||
.ctx-menu.show { display:block; }
|
||
.ctx-menu .ctx-item { padding:8px 16px;cursor:pointer;font-size:12px;color:var(--text-secondary);transition:var(--transition);display:flex;align-items:center;gap:8px; }
|
||
.ctx-menu .ctx-item:hover { background:var(--bg-hover);color:#fff; }
|
||
.ctx-divider { height:1px;background:var(--border);margin:4px 0; }
|
||
.export-options { display:flex;flex-direction:column;gap:8px; }
|
||
.export-opt { display:flex;align-items:center;padding:10px 14px;border-radius:var(--radius);background:var(--bg-tertiary);cursor:pointer;border:1px solid var(--border);transition:var(--transition);gap:10px; }
|
||
.export-opt:hover { border-color:var(--accent);background:var(--bg-hover); }
|
||
.export-opt .export-icon { font-size:20px;width:36px;text-align:center; }
|
||
.export-opt .export-info { flex:1; }
|
||
.export-opt .export-name { font-weight:600;font-size:13px;color:#fff; }
|
||
.export-opt .export-desc { font-size:10px;color:var(--text-muted); }
|
||
.license-notice { padding:8px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:10px;color:var(--text-muted);line-height:1.6;margin-top:8px; }
|
||
.license-notice a { color:var(--accent-light); }
|
||
.template-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px; }
|
||
.template-card { padding:14px;border-radius:var(--radius);background:var(--bg-tertiary);border:1px solid var(--border);cursor:pointer;transition:var(--transition); }
|
||
.template-card:hover { border-color:var(--accent);background:var(--bg-hover); }
|
||
.template-card .tpl-name { font-weight:700;font-size:13px;color:#fff;margin-bottom:6px; }
|
||
.template-card .tpl-preview { font-size:10px;color:var(--text-muted);line-height:1.5;font-family:var(--font-mono); }
|
||
.template-card .tpl-badge { font-size:9px;padding:2px 6px;border-radius:8px;display:inline-block;margin-top:6px; }
|
||
.tpl-badge.builtin { background:var(--accent);color:#fff; } .tpl-badge.custom { background:var(--gold);color:#000; }
|
||
.onboarding-slide { text-align:center;padding:20px 0; }
|
||
.onboarding-slide .ob-icon { font-size:56px;margin-bottom:16px; }
|
||
.onboarding-slide h2 { font-size:20px;color:#fff;margin-bottom:8px; }
|
||
.onboarding-slide p { font-size:13px;color:var(--text-muted);line-height:1.8; }
|
||
.onboarding-dots { display:flex;gap:6px;justify-content:center;padding:10px; }
|
||
.onboarding-dots .dot { width:8px;height:8px;border-radius:50%;background:var(--border);transition:var(--transition); }
|
||
.onboarding-dots .dot.active { background:var(--accent);width:24px;border-radius:4px; }
|
||
#focusSoundIndicator { display:none;position:fixed;bottom:20px;left:20px;font-size:11px;color:var(--text-dim);z-index:999;align-items:center;gap:6px; }
|
||
#focusSoundIndicator .sound-icon { animation:pulse 2s infinite; }
|
||
.streak-badges { display:flex;gap:8px;padding:8px 48px 16px;flex-wrap:wrap; }
|
||
.streak-badge { padding:6px 14px;border-radius:20px;font-size:11px;font-weight:600;border:1px solid var(--border);background:var(--bg-tertiary);color:var(--text-secondary);display:flex;align-items:center;gap:6px; }
|
||
.streak-badge.earned { border-color:var(--gold);color:var(--gold);background:rgba(240,192,96,0.1); }
|
||
.vol-header .vol-menu-btn { opacity:0;font-size:10px;color:var(--text-muted);cursor:pointer;padding:2px 6px;border-radius:3px; }
|
||
.vol-header:hover .vol-menu-btn { opacity:1; }
|
||
.vol-header .vol-menu-btn:hover { background:var(--bg-active);color:#fff; }
|
||
.setting-cat .add-btn { color:var(--accent-light);cursor:pointer;font-weight:400; }
|
||
.retry-btn { display:none;padding:4px 10px;border-radius:10px;border:1px solid var(--orange);background:rgba(232,144,80,0.15);color:var(--orange);cursor:pointer;font-size:10px; }
|
||
.retry-btn.show { display:inline-block; }
|
||
.settings-content .shortcut-key { display:inline-block;padding:2px 8px;border-radius:3px;background:var(--bg-active);font-family:var(--font-mono);font-size:11px;border:1px solid var(--border);cursor:pointer;min-width:80px;text-align:center; }
|
||
.settings-content .shortcut-key.recording { border-color:var(--red);color:var(--red);animation:pulse-tts 1s infinite; }
|
||
#topbar .top-btn .badge-dot { position:absolute;top:4px;right:4px;width:6px;height:6px;border-radius:50%;background:var(--red); }
|
||
#editor-toolbar .word-count-display { cursor:pointer; }
|
||
#left-sidebar .sidebar-footer button.accent { color:var(--accent-light);border-color:var(--accent-dark); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div id="app">
|
||
<!-- ==================== TOP BAR ==================== -->
|
||
<div id="topbar">
|
||
<span class="logo-area" onclick="goHome()" title="返回主页">
|
||
✒ 笔临
|
||
</span>
|
||
<div id="tabContainer" style="display:flex;align-items:center;gap:2px;flex:1;overflow-x:auto;">
|
||
<!-- 动态生成tabs -->
|
||
</div>
|
||
<div id="dailyGoalBar" title="今日写作目标" aria-label="日更进度">
|
||
<span class="goal-text">今日</span>
|
||
<span class="goal-track"><span class="goal-fill" id="goalFill" style="width:42%"></span></span>
|
||
<span class="goal-text" id="goalLabel">1,280/3,000</span>
|
||
</div>
|
||
<div class="spacer"></div>
|
||
<div style="position:relative;-webkit-app-region:no-drag;">
|
||
<button class="top-btn" id="btnSound" title="专注音效" aria-label="专注音效" onclick="toggleSoundPopover()"><i class="fa-solid fa-music"></i></button>
|
||
<div class="sound-popover" id="soundPopover">
|
||
<div class="sound-item active" onclick="selectSound('none',this)"><i class="fa-solid fa-volume-xmark"></i> 无音效</div>
|
||
<div class="sound-divider"></div>
|
||
<div class="sound-item" onclick="selectSound('rain',this)"><i class="fa-solid fa-cloud-rain"></i> 雨声 <input type="range" class="vol-slider" min="0" max="100" value="60" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('keyboard',this)"><i class="fa-solid fa-keyboard"></i> 键盘声 <input type="range" class="vol-slider" min="0" max="100" value="40" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('cafe',this)"><i class="fa-solid fa-mug-hot"></i> 咖啡馆 <input type="range" class="vol-slider" min="0" max="100" value="50" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('fire',this)"><i class="fa-solid fa-fire"></i> 篝火 <input type="range" class="vol-slider" min="0" max="100" value="45" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('forest',this)"><i class="fa-solid fa-tree"></i> 森林 <input type="range" class="vol-slider" min="0" max="100" value="55" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('ocean',this)"><i class="fa-solid fa-water"></i> 海浪 <input type="range" class="vol-slider" min="0" max="100" value="50" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-item" onclick="selectSound('white',this)"><i class="fa-solid fa-wave-square"></i> 白噪音 <input type="range" class="vol-slider" min="0" max="100" value="40" onclick="event.stopPropagation()"></div>
|
||
<div class="sound-divider"></div>
|
||
<div class="sound-item" onclick="showToast('选择自定义音频文件...')"><i class="fa-solid fa-plus"></i> 自定义音频…</div>
|
||
</div>
|
||
</div>
|
||
<button class="top-btn" title="系统设置" onclick="openSettingsTab()" aria-label="系统设置"><i class="fa-solid fa-gear"></i></button>
|
||
<button class="top-btn" title="专注模式 Ctrl+Alt+F" onclick="toggleFocusMode()" aria-label="专注模式"><i class="fa-solid fa-eye"></i></button>
|
||
<button class="top-btn" title="驾驶舱" onclick="showDashboardModal()" aria-label="驾驶舱"><i class="fa-solid fa-gauge-high"></i></button>
|
||
<button class="top-btn" title="关系图谱" onclick="showGraphModal()" aria-label="角色关系图谱"><i class="fa-solid fa-diagram-project"></i></button>
|
||
<button class="top-btn" title="故事时间线" onclick="showTimelineModal()" aria-label="故事时间线"><i class="fa-solid fa-timeline"></i></button>
|
||
<button class="top-btn window-btn" id="btn-minimize" title="最小化" aria-label="最小化窗口"><i class="fa-solid fa-window-minimize"></i></button>
|
||
<button class="top-btn window-btn" id="btn-maximize" title="最大化"><i class="fa-solid fa-window-maximize"></i></button>
|
||
<button class="top-btn window-btn window-close-btn" id="btn-close" title="关闭"><i class="fa-solid fa-xmark"></i></button>
|
||
</div>
|
||
|
||
<!-- ==================== MAIN AREA ==================== -->
|
||
<div id="main-area">
|
||
<!-- HOME PAGE -->
|
||
<div id="home-page">
|
||
<div class="home-hero">
|
||
<h1>✒ 笔临</h1>
|
||
<p class="subtitle">长篇创作智能协作平台 · 让每一个故事都被认真对待</p>
|
||
</div>
|
||
<div class="home-actions">
|
||
<button class="btn-large primary" onclick="showNewBookModal()"><i class="fa-solid fa-plus"></i> 新建书籍</button>
|
||
<button class="btn-large" onclick="showImportModal()"><i class="fa-solid fa-file-import"></i> 导入书籍</button>
|
||
<button class="btn-large" onclick="showExportAllModal()"><i class="fa-solid fa-box-archive"></i> 导出所有书籍</button>
|
||
<button class="btn-large" onclick="openSettingsTab()"><i class="fa-solid fa-gear"></i> 系统设置</button>
|
||
</div>
|
||
<div class="streak-badges" id="streakBadges">
|
||
<span class="streak-badge earned">🔥 连续7天</span>
|
||
<span class="streak-badge earned">🏆 30天里程碑</span>
|
||
<span class="streak-badge">💯 100天挑战 (32/100)</span>
|
||
</div>
|
||
<div class="home-section">
|
||
<div class="section-header">
|
||
<span class="section-title">📚 我的书籍 <span class="section-count" id="bookCount">2本</span></span>
|
||
<span style="font-size:11px;color:var(--text-muted);cursor:pointer;">排序 ▾</span>
|
||
</div>
|
||
<div class="book-grid" id="bookGrid">
|
||
<!-- 书籍卡片动态生成 -->
|
||
</div>
|
||
</div>
|
||
<div class="home-section">
|
||
<div class="section-header">
|
||
<span class="section-title">🕐 最近编辑</span>
|
||
</div>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;" id="recentList">
|
||
<!-- 最近编辑动态生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EDITOR LAYOUT -->
|
||
<div id="editor-layout" class="hidden">
|
||
<!-- LEFT SIDEBAR -->
|
||
<div id="left-sidebar">
|
||
<div class="sidebar-header">
|
||
<span class="book-title" id="sidebarBookTitle">📖 仙道长青</span>
|
||
<span class="badge" id="sidebarBookBadge">连载中</span>
|
||
</div>
|
||
<div class="sidebar-nav">
|
||
<button class="nav-btn active" data-sidebar-panel="chapters-panel" onclick="switchSidebarPanel('chapters-panel',this)">📑 章节</button>
|
||
<button class="nav-btn" data-sidebar-panel="outline-panel" onclick="switchSidebarPanel('outline-panel',this)">🗺 大纲</button>
|
||
<button class="nav-btn" data-sidebar-panel="setting-panel" onclick="switchSidebarPanel('setting-panel',this)">📋 设定</button>
|
||
<button class="nav-btn" data-sidebar-panel="inspiration-panel" onclick="switchSidebarPanel('inspiration-panel',this)">💡 灵感</button>
|
||
</div>
|
||
<!-- 章节面板 -->
|
||
<div class="sidebar-panel active" id="chapters-panel">
|
||
<div id="batchToolbar" role="toolbar" aria-label="批量操作">
|
||
<span id="batchCount">已选 0 章</span>
|
||
<button onclick="batchSetStatus('draft')" aria-label="批量标为草稿">草稿</button>
|
||
<button onclick="batchSetStatus('review')" aria-label="批量标为需修">需修</button>
|
||
<button onclick="batchSetStatus('done')" aria-label="批量标为定稿">定稿</button>
|
||
<button onclick="batchExport()" aria-label="批量导出">导出</button>
|
||
<button onclick="clearBatchSelection()" style="margin-left:auto;" aria-label="取消选择">✕</button>
|
||
</div>
|
||
<div id="chapter-tree">
|
||
<div class="vol-group">
|
||
<div class="vol-header open" onclick="toggleVol(this)" oncontextmenu="showVolContextMenu(event,this);return false;">
|
||
<span class="vol-arrow">▶</span> 第一卷·青云之始
|
||
<span class="vol-count">12章 · 3.8万字</span>
|
||
<span class="vol-menu-btn" onclick="event.stopPropagation();showVolContextMenu(event,this.parentElement);" aria-label="分卷菜单">⋯</span>
|
||
</div>
|
||
<div class="vol-chapters">
|
||
<div class="chapter-item" data-ch="1" onclick="selectChapter(this,1)" tabindex="0" role="treeitem" aria-label="第1章 山村少年 定稿">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,1)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第1章</span><span class="ch-title">山村少年</span><span class="ch-badge done">定稿</span>
|
||
</div>
|
||
<div class="chapter-item" data-ch="2" onclick="selectChapter(this,2)" tabindex="0" role="treeitem" aria-label="第2章 意外觉醒 定稿">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,2)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第2章</span><span class="ch-title">意外觉醒</span><span class="ch-badge done">定稿</span>
|
||
</div>
|
||
<div class="chapter-item active" data-ch="3" onclick="selectChapter(this,3)" tabindex="0" role="treeitem" aria-label="第3章 入门考验 AI初稿">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,3)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第3章</span><span class="ch-title">入门考验</span><span class="ch-badge ai">AI初稿</span>
|
||
</div>
|
||
<div class="chapter-item" data-ch="4" onclick="selectChapter(this,4)" tabindex="0" role="treeitem" aria-label="第4章 灵根测试 草稿">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,4)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第4章</span><span class="ch-title">灵根测试</span><span class="ch-badge draft">草稿</span>
|
||
</div>
|
||
<div class="chapter-item" data-ch="5" onclick="selectChapter(this,5)" tabindex="0" role="treeitem" aria-label="第5章 初次修炼 需修">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,5)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第5章</span><span class="ch-title">初次修炼</span><span class="ch-badge review">需修</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="vol-group">
|
||
<div class="vol-header" onclick="toggleVol(this)" oncontextmenu="showVolContextMenu(event,this);return false;">
|
||
<span class="vol-arrow">▶</span> 第二卷·宗门风云
|
||
<span class="vol-count">8章 · 2.4万字</span>
|
||
<span class="vol-menu-btn" onclick="event.stopPropagation();showVolContextMenu(event,this.parentElement);" aria-label="分卷菜单">⋯</span>
|
||
</div>
|
||
<div class="vol-chapters hidden">
|
||
<div class="chapter-item" data-ch="13" onclick="selectChapter(this,13)" tabindex="0" role="treeitem" aria-label="第13章 初入宗门 定稿">
|
||
<span class="ch-select" onclick="event.stopPropagation();toggleChapterSelect(this,13)" aria-label="选择章节"></span>
|
||
<span class="ch-num">第13章</span><span class="ch-title">初入宗门</span><span class="ch-badge done">定稿</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-footer">
|
||
<button onclick="addChapter()" aria-label="新建章节"><i class="fa-solid fa-plus"></i> 新章</button>
|
||
<button onclick="showTemplateModal()" aria-label="模板快速新建"><i class="fa-solid fa-file-lines"></i> 模板新建</button>
|
||
<button onclick="addVolume()" aria-label="新建分卷"><i class="fa-solid fa-folder-plus"></i> 新卷</button>
|
||
<button onclick="showVersionModal()" aria-label="版本历史"><i class="fa-solid fa-clock-rotate-left"></i> 版本</button>
|
||
<button class="accent" onclick="enterReadingMode()" aria-label="阅读模式"><i class="fa-solid fa-book-open"></i> 阅读</button>
|
||
</div>
|
||
</div>
|
||
<!-- 大纲面板 -->
|
||
<div class="sidebar-panel" id="outline-panel" style="padding:4px;">
|
||
<div style="padding:4px 8px;display:flex;gap:4px;flex-shrink:0;">
|
||
<button class="btn" style="font-size:10px;padding:3px 8px;background:var(--accent);color:#fff;">全部</button>
|
||
<button class="btn" style="font-size:10px;padding:3px 8px;">未覆盖</button>
|
||
<button class="btn" style="font-size:10px;padding:3px 8px;">已覆盖</button>
|
||
</div>
|
||
<div style="padding:4px 8px;font-size:10px;color:var(--text-muted);">大纲树(拖拽排序 · 点击关联章节)</div>
|
||
<div class="outline-item uncovered" title="预期 3000字 vs 实际 0字 — 偏差100%">⚠ 第一卷核心剧情线</div>
|
||
<div class="outline-item uncovered" style="padding-left:28px;" title="预期 2000字 vs 实际 0字 — 偏差100%">⚠ 林远觉醒三纹灵根</div>
|
||
<div class="outline-item" style="padding-left:28px;color:var(--accent-light);">▶ 入门考验(写作中 — 第3章)</div>
|
||
<div class="outline-item" style="padding-left:28px;color:var(--green);">✓ 拜入青云宗(第13章 · 2,100字)</div>
|
||
<div style="padding:8px;text-align:center;font-size:10px;color:var(--text-dim);">右键大纲条目 → "生成章节骨架"</div>
|
||
</div>
|
||
<!-- 设定面板(书籍设定:人物、技能、世界观、场景) -->
|
||
<div class="sidebar-panel" id="setting-panel">
|
||
<div class="setting-cat">👤 人物角色 <span style="float:right;font-weight:400;color:var(--accent-light);cursor:pointer;">+添加</span></div>
|
||
<div class="setting-entry" onclick="showToast('打开角色详情:林远')">
|
||
<span class="entry-icon char">👤</span><span class="entry-name">林远</span><span class="entry-count">主角</span>
|
||
</div>
|
||
<div class="setting-entry" onclick="showToast('打开角色详情:白发长老')">
|
||
<span class="entry-icon char">🧓</span><span class="entry-name">白发长老</span><span class="entry-count">配角</span>
|
||
</div>
|
||
<div class="setting-entry" onclick="showToast('打开角色详情:柳青烟')">
|
||
<span class="entry-icon char">👩</span><span class="entry-name">柳青烟</span><span class="entry-count">女主</span>
|
||
</div>
|
||
<div class="setting-cat">⚔ 技能功法 <span style="float:right;font-weight:400;color:var(--accent-light);cursor:pointer;">+添加</span></div>
|
||
<div class="setting-entry" onclick="showToast('打开技能详情:青云诀')">
|
||
<span class="entry-icon skill">📜</span><span class="entry-name">青云诀</span><span class="entry-count">核心功法</span>
|
||
</div>
|
||
<div class="setting-entry" onclick="showToast('打开技能详情:玄火掌')">
|
||
<span class="entry-icon skill">🔥</span><span class="entry-name">玄火掌</span><span class="entry-count">三阶段</span>
|
||
</div>
|
||
<div class="setting-cat">🌍 世界观 <span style="float:right;font-weight:400;color:var(--accent-light);cursor:pointer;">+添加</span></div>
|
||
<div class="setting-entry" onclick="showToast('打开世界观条目:九州大陆')">
|
||
<span class="entry-icon world">🗺</span><span class="entry-name">九州大陆</span><span class="entry-count">地理</span>
|
||
</div>
|
||
<div class="setting-entry" onclick="showToast('打开世界观条目:灵根体系')">
|
||
<span class="entry-icon world">✨</span><span class="entry-name">灵根体系</span><span class="entry-count">力量体系</span>
|
||
</div>
|
||
<div class="setting-cat">🏞 场景地点 <span style="float:right;font-weight:400;color:var(--accent-light);cursor:pointer;">+添加</span></div>
|
||
<div class="setting-entry" onclick="showToast('打开场景详情:青云宗演武场')">
|
||
<span class="entry-icon scene">🏟</span><span class="entry-name">青云宗演武场</span><span class="entry-count">第3章</span>
|
||
</div>
|
||
<div class="setting-entry" onclick="showToast('打开场景详情:青石村')">
|
||
<span class="entry-icon scene">🏡</span><span class="entry-name">青石村</span><span class="entry-count">第1章</span>
|
||
</div>
|
||
</div>
|
||
<!-- 灵感面板 -->
|
||
<div class="sidebar-panel" id="inspiration-panel" style="padding:8px;">
|
||
<div style="padding:8px;color:var(--text-muted);font-size:11px;text-align:center;">💡 灵感碎片(点击可转为大纲或设定)</div>
|
||
<div style="padding:6px 8px;cursor:pointer;border-radius:4px;font-size:12px;color:var(--text-secondary);background:var(--bg-tertiary);margin:2px 0;">林远的身世可能与太上长老有关...</div>
|
||
<div style="padding:6px 8px;cursor:pointer;border-radius:4px;font-size:12px;color:var(--text-secondary);background:var(--bg-tertiary);margin:2px 0;">考虑在第三卷加入妖兽潮事件</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EDITOR AREA -->
|
||
<div id="editor-area">
|
||
<div id="editor-toolbar">
|
||
<button class="tool-btn" title="加粗"><b>B</b></button>
|
||
<button class="tool-btn" title="斜体"><i>I</i></button>
|
||
<button class="tool-btn" title="下划线"><u>U</u></button>
|
||
<span class="sep"></span>
|
||
<button class="tool-btn" title="标题">H</button>
|
||
<button class="tool-btn" title="引用">❝</button>
|
||
<span class="sep"></span>
|
||
<button class="tool-btn" title="撤销 Ctrl+Z" aria-label="撤销"><i class="fa-solid fa-rotate-left"></i></button>
|
||
<button class="tool-btn" title="重做 Ctrl+Y" aria-label="重做"><i class="fa-solid fa-rotate-right"></i></button>
|
||
<span class="sep"></span>
|
||
<button class="tool-btn" id="btnSpellCheck" title="拼写检查" aria-label="拼写检查" onclick="toggleSpellCheck()"><i class="fa-solid fa-spell-check"></i></button>
|
||
<button class="tool-btn" id="btnTTS" title="朗读 Ctrl+Shift+R" aria-label="朗读" onclick="toggleTTS()"><i class="fa-solid fa-headphones"></i></button>
|
||
<span class="sep"></span>
|
||
<span class="ch-label">当前:第3章 · 入门考验</span>
|
||
<span class="word-count-display" id="wordCountDisplay" title="点击查看用词分析" onclick="showWordFreqInPanel()">2,847 字</span>
|
||
<button class="tool-btn" title="保存快照 Ctrl+Alt+S" aria-label="保存快照" onclick="saveSnapshot()"><i class="fa-solid fa-camera"></i></button>
|
||
<button class="tool-btn" title="导出当前章节" aria-label="导出" onclick="showExportModal()"><i class="fa-solid fa-download"></i></button>
|
||
</div>
|
||
<div id="editor-content" contenteditable="true" oninput="updateWordCount()">
|
||
晨光透过稀疏的竹帘,在青石地面上投下细碎的光斑。
|
||
|
||
林远深吸一口气,推开木门走了出去。门外是一个宽阔的演武场,已经有数十名少年等在那里。他们的脸上有期待,有紧张,也有毫不掩饰的傲气。
|
||
|
||
"下一个,林远。"
|
||
|
||
听到自己的名字,他迈步走向那块通体漆黑的测灵石。周围的目光齐刷刷地落在他身上——这个来自偏远山村的少年,在入门考核中表现平平,谁也不曾把他放在眼里。
|
||
|
||
他将手掌贴上冰冷的石面。
|
||
|
||
刹那间,刺目的青光冲天而起,测灵石上浮现出三道清晰的光纹。全场鸦雀无声。
|
||
|
||
"三纹灵根……"负责测试的白发长老眯起眼睛,语气中带着一丝惊讶,"有意思。"
|
||
</div>
|
||
<div id="editor-statusbar">
|
||
<span>📝 总字数:<b>128,450</b></span>
|
||
<span>📖 本章:<b>2,847</b> / 提醒 3,000</span>
|
||
<span>📊 本卷:<b>38,200</b></span>
|
||
<span class="speed-indicator">⚡ 今日:1,280字 · 速度 42字/分</span>
|
||
<span class="pomodoro" id="pomodoroTimer" onclick="togglePomodoro()" title="番茄钟 25分钟" aria-label="番茄钟">🍅 25:00</span>
|
||
<span style="margin-left:auto;">💾 已自动保存</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- RIGHT PANEL -->
|
||
<div id="right-panel">
|
||
<div class="panel-tabs">
|
||
<button class="panel-tab active" onclick="switchRightPanel('ai-chat',this)">🤖 AI助手</button>
|
||
<button class="panel-tab" onclick="switchRightPanel('knowledge',this)">🧠 知识库</button>
|
||
<button class="panel-tab" onclick="switchRightPanel('wordfreq',this)">📊 用词</button>
|
||
<button class="panel-tab" onclick="switchRightPanel('book-settings-panel',this)">⚙ 设置</button>
|
||
</div>
|
||
<!-- AI Chat Panel -->
|
||
<div class="panel-content active" id="ai-chat">
|
||
<div class="offline-banner" id="offlineBanner">⚠ 当前网络不可用,已切换为离线模式。请配置本地模型或连接网络。</div>
|
||
<div class="chat-messages" id="chatMessages">
|
||
<div class="chat-msg ai">
|
||
<b>笔临AI</b> 已就绪。<br>当前上下文:第1-2章剧情摘要、大纲条目3条、设定条目7个。
|
||
<br><br>检测到您正在编辑<em>第3章·入门考验</em>。需要我为您提供接下来的剧情走向建议吗?
|
||
<div class="plot-options">
|
||
<div class="plot-opt" onclick="selectPlot('A')">
|
||
<span class="opt-label">走向A · 锋芒毕露</span><br>林远的三纹灵根引起轰动,被直接提拔为内门弟子,但引来同门嫉妒。
|
||
</div>
|
||
<div class="plot-opt" onclick="selectPlot('B')">
|
||
<span class="opt-label">走向B · 藏锋守拙</span><br>长老暗示他隐藏真实天赋,林远被安排在外门低调修行。
|
||
</div>
|
||
<div class="plot-opt" onclick="selectPlot('C')">
|
||
<span class="opt-label">走向C · 意外转折</span><br>测灵石碎裂,林远灵力引发异象,惊动闭关中的太上长老。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="quick-cmds">
|
||
<span class="quick-cmd" onclick="quickCmd('/续写')">/续写</span>
|
||
<span class="quick-cmd" onclick="quickCmd('/扩写')">/扩写</span>
|
||
<span class="quick-cmd" onclick="quickCmd('/润色')">/润色</span>
|
||
<span class="quick-cmd" onclick="quickCmd('/总结')">/总结</span>
|
||
<span class="quick-cmd" onclick="quickCmd('/纠错')">/纠错</span>
|
||
</div>
|
||
<div class="chat-input-area">
|
||
<input type="text" id="chatInput" placeholder="输入指令或选择上方快捷命令..." onkeydown="if(event.key==='Enter')sendMessage()">
|
||
<button onclick="sendMessage()">发送</button>
|
||
</div>
|
||
</div>
|
||
<!-- Knowledge Panel -->
|
||
<div class="panel-content" id="knowledge">
|
||
<div style="padding:10px;border-bottom:1px solid var(--border);display:flex;gap:6px;flex-shrink:0;">
|
||
<button class="btn" style="flex:1;font-size:10px;" onclick="showNamingModal()">🔍 命名一致性检查</button>
|
||
</div>
|
||
<div style="padding:8px 12px;font-size:10px;color:var(--text-muted);border-bottom:1px solid var(--border);">
|
||
伏笔:<b style="color:var(--gold);">已埋 8</b> · <b style="color:var(--green);">已解 3</b> · <b style="color:var(--red);">疑似遗忘 1</b>
|
||
</div>
|
||
<div class="kb-item"><span>🔗</span><span style="flex:1;">伏笔:测灵石异常发热</span><span class="kb-stars">★★★★</span><span class="kb-tag">第3章</span></div>
|
||
<div class="kb-item"><span>👤</span><span style="flex:1;">角色:林远 - 三纹灵根觉醒</span><span class="kb-stars">★★★★★</span><span class="kb-tag">状态变更</span></div>
|
||
<div class="kb-item"><span>📍</span><span style="flex:1;">地点:青云宗演武场</span><span class="kb-stars">★★★</span><span class="kb-tag">新地点</span></div>
|
||
<div class="kb-item"><span>🤝</span><span style="flex:1;">关系:林远↔白发长老(注意)</span><span class="kb-stars">★★★★</span><span class="kb-tag">新关系</span></div>
|
||
</div>
|
||
<!-- 用词分析面板 -->
|
||
<div class="panel-content" id="wordfreq">
|
||
<div class="wf-header">
|
||
<button class="active">高频词</button><button>高频短语</button><button>过度用词</button>
|
||
</div>
|
||
<div style="flex:1;overflow-y:auto;padding:6px;">
|
||
<div class="wf-row"><span class="wf-rank">1</span><span class="wf-word">林远</span><span class="wf-bar"><span class="wf-bar-fill" style="width:100%"></span></span><span class="wf-count">847</span></div>
|
||
<div class="wf-row"><span class="wf-rank">2</span><span class="wf-word">灵力</span><span class="wf-bar"><span class="wf-bar-fill" style="width:72%"></span></span><span class="wf-count">612</span></div>
|
||
<div class="wf-row overused"><span class="wf-rank">3</span><span class="wf-word">深吸一口气</span><span class="wf-bar"><span class="wf-bar-fill" style="width:58%"></span></span><span class="wf-count">48</span></div>
|
||
<div class="wf-row"><span class="wf-rank">4</span><span class="wf-word">长老</span><span class="wf-bar"><span class="wf-bar-fill" style="width:51%"></span></span><span class="wf-count">430</span></div>
|
||
<div class="wf-row"><span class="wf-rank">5</span><span class="wf-word">冷笑</span><span class="wf-bar"><span class="wf-bar-fill" style="width:44%"></span></span><span class="wf-count">37</span></div>
|
||
<div class="wf-row"><span class="wf-rank">6</span><span class="wf-word">测灵石</span><span class="wf-bar"><span class="wf-bar-fill" style="width:38%"></span></span><span class="wf-count">28</span></div>
|
||
<div class="wf-row"><span class="wf-rank">7</span><span class="wf-word">演武场</span><span class="wf-bar"><span class="wf-bar-fill" style="width:34%"></span></span><span class="wf-count">22</span></div>
|
||
<div class="wf-row"><span class="wf-rank">8</span><span class="wf-word">天赋</span><span class="wf-bar"><span class="wf-bar-fill" style="width:30%"></span></span><span class="wf-count">19</span></div>
|
||
</div>
|
||
</div>
|
||
<!-- 书籍设置面板 -->
|
||
<div class="panel-content" id="book-settings-panel">
|
||
<div class="settings-group">
|
||
<h4>🎨 文风控制面板</h4>
|
||
<div class="settings-row"><span>平均句长</span><input type="range" min="10" max="60" value="28"><span>28字</span></div>
|
||
<div class="settings-row"><span>对话占比</span><input type="range" min="10" max="80" value="37"><span>37%</span></div>
|
||
<div class="settings-row"><span>比喻密度</span><input type="range" min="0" max="100" value="65"><span>高</span></div>
|
||
<div class="settings-row"><span>禁用"冷笑"</span><input type="range" min="0" max="100" value="0"><span>关闭</span></div>
|
||
</div>
|
||
<div class="settings-group">
|
||
<h4>📝 版本设置</h4>
|
||
<div class="settings-row"><span>自动快照间隔</span><input type="number" value="5" min="1" max="30">分钟</div>
|
||
<div class="settings-row"><span>临时版本上限</span><input type="number" value="20" min="5" max="100">个</div>
|
||
<div class="settings-row"><span>持久版本上限</span><input type="number" value="3" min="1" max="20">个</div>
|
||
</div>
|
||
<div class="settings-group">
|
||
<h4>🎯 字数提醒</h4>
|
||
<div class="settings-row"><span>章节提醒阈值</span><input type="number" value="3000" min="500" max="20000">字</span></div>
|
||
</div>
|
||
<div class="settings-group">
|
||
<h4>📖 书籍信息</h4>
|
||
<div class="settings-row"><span>分类标签</span><select><option>玄幻</option><option>仙侠</option><option>武侠</option></select></div>
|
||
<div class="settings-row"><span>目标总字数</span><input type="number" value="500000" min="10000">字</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SETTINGS PAGE (as tab) -->
|
||
<div id="settings-page" class="hidden">
|
||
<div class="settings-layout">
|
||
<div class="settings-sidebar">
|
||
<div class="settings-nav-item active" onclick="switchSettingsNav('general',this)">⚙ 通用</div>
|
||
<div class="settings-nav-item" onclick="switchSettingsNav('editor',this)">📝 编辑器</div>
|
||
<div class="settings-nav-item" onclick="switchSettingsNav('ai',this)">🤖 AI配置</div>
|
||
<div class="settings-nav-item" onclick="switchSettingsNav('shortcuts',this)">⌨ 快捷键</div>
|
||
<div class="settings-nav-item" onclick="switchSettingsNav('backup',this)">💾 备份与同步</div>
|
||
<div class="settings-nav-item" onclick="switchSettingsNav('about',this)">ℹ 关于</div>
|
||
</div>
|
||
<div class="settings-content" id="settingsContent">
|
||
<!-- 通用设置 -->
|
||
<div class="settings-section" id="settings-general">
|
||
<h2>⚙ 通用设置</h2>
|
||
<div class="setting-block">
|
||
<h4>语言与外观</h4>
|
||
<div class="setting-item"><span class="setting-label">界面语言</span><select><option>简体中文</option><option>繁體中文</option><option>English</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">主题</span><select id="themeSelect" onchange="switchTheme(this.value)" aria-label="主题选择"><option value="default">墨砚(默认)</option><option value="bamboo">竹语</option><option value="moonlit">月夜</option><option value="ricepaper">宣纸</option><option value="mist">晨雾</option><option value="teagarden">茶园</option><option value="twilight">暮光</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">字体大小</span><select><option>中</option><option>小</option><option>大</option></select></div>
|
||
</div>
|
||
<div class="setting-block">
|
||
<h4>默认行为</h4>
|
||
<div class="setting-item"><span class="setting-label">启动时打开上次书籍</span><select><option>是</option><option>否(显示主页)</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">自动保存间隔</span><input type="number" value="2" min="1" max="10">秒</div>
|
||
</div>
|
||
</div>
|
||
<!-- 编辑器设置 -->
|
||
<div class="settings-section hidden" id="settings-editor">
|
||
<h2>📝 编辑器设置</h2>
|
||
<div class="setting-block">
|
||
<h4>排版</h4>
|
||
<div class="setting-item"><span class="setting-label">默认字体</span><select><option>宋体</option><option>楷体</option><option>微软雅黑</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">行距</span><select><option>1.8</option><option>2.0</option><option>2.2</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">专注模式背景</span><select><option>纯黑</option><option>深灰</option><option>渐变</option></select></div>
|
||
</div>
|
||
</div>
|
||
<!-- AI设置 -->
|
||
<div class="settings-section hidden" id="settings-ai">
|
||
<h2>🤖 AI配置</h2>
|
||
<div class="setting-block">
|
||
<h4>服务配置</h4>
|
||
<div class="setting-item"><span class="setting-label">AI引擎</span><select><option>本地模型</option><option>云端API</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">API密钥</span><input type="password" value="••••••••" style="width:180px;"></div>
|
||
<div class="setting-item"><span class="setting-label">超时时间</span><input type="number" value="60" min="10" max="300">秒</div>
|
||
<div class="setting-item"><span class="setting-label">数据上传</span><select><option>禁止(纯本地)</option><option>允许(增强学习)</option></select></div>
|
||
</div>
|
||
<div class="setting-block">
|
||
<h4>默认Skill</h4>
|
||
<div class="setting-item"><span class="setting-label">当前默认Skill</span><select><option>玄幻热血</option><option>悬疑深沉</option><option>轻松日常</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">导入自定义Skill</span><button class="btn" style="font-size:10px;">选择文件</button></div>
|
||
</div>
|
||
<div class="license-notice">⚠ 使用云端 AI 服务将受到相应模型提供商的许可协议约束。请在使用前阅读并同意 <a href="#" onclick="event.preventDefault();showToast('打开OpenAI服务条款')">OpenAI 服务条款</a> / <a href="#" onclick="event.preventDefault();showToast('打开Anthropic服务条款')">Anthropic 服务条款</a> 等。</div>
|
||
</div>
|
||
<!-- 快捷键设置 -->
|
||
<div class="settings-section hidden" id="settings-shortcuts">
|
||
<h2>⌨ 快捷键设置</h2>
|
||
<div class="setting-block"><h4>编辑器快捷键</h4>
|
||
<div class="setting-item"><span class="setting-label">保存当前章节</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+S</span></div>
|
||
<div class="setting-item"><span class="setting-label">聚焦AI输入框</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+K</span></div>
|
||
<div class="setting-item"><span class="setting-label">新建章节</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+N</span></div>
|
||
<div class="setting-item"><span class="setting-label">切换专注模式</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Alt+F</span></div>
|
||
<div class="setting-item"><span class="setting-label">全局搜索</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+F</span></div>
|
||
<div class="setting-item"><span class="setting-label">朗读 播放/暂停</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+R</span></div>
|
||
<div class="setting-item"><span class="setting-label">灵感快速捕获</span><span class="shortcut-key" onclick="recordShortcut(this)">Ctrl+Shift+I</span></div>
|
||
</div>
|
||
</div>
|
||
<!-- 备份与同步 -->
|
||
<div class="settings-section hidden" id="settings-backup">
|
||
<h2>💾 备份与同步</h2>
|
||
<div class="setting-block">
|
||
<h4>本地备份</h4>
|
||
<div class="setting-item"><span class="setting-label">自动备份间隔</span><select><option>每天</option><option>每周</option><option>手动</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">备份保留数量</span><input type="number" value="10" min="1" max="100"></div>
|
||
<div class="setting-item"><span class="setting-label">立即备份</span><button class="btn primary" style="font-size:10px;">执行备份</button></div>
|
||
<div class="setting-block">
|
||
<h4>☁ 云同步</h4>
|
||
<div class="setting-item"><span class="setting-label">同步方式</span><select><option>关闭</option><option>WebDAV</option><option>本地目录</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">服务器地址</span><input type="text" placeholder="https://dav.example.com/novels/" style="width:200px;"></div>
|
||
<div class="setting-item"><span class="setting-label">同步密码</span><input type="password" placeholder="至少8位" style="width:150px;"></div>
|
||
<div class="setting-item"><span class="setting-label">定时同步</span><select><option>手动</option><option>每小时</option><option>每天</option></select></div>
|
||
<div class="setting-item"><span class="setting-label">立即同步</span><button class="btn primary" style="font-size:10px;" onclick="showToast('🔒 加密同步中...')">立即同步</button></div>
|
||
<div class="setting-item"><span class="setting-desc">数据以 AES-256-GCM 加密,密钥由同步密码派生,不存储明文。</span></div>
|
||
</div>
|
||
</div>
|
||
<!-- 关于 -->
|
||
<div class="settings-section hidden" id="settings-about">
|
||
<h2>ℹ 关于笔临</h2>
|
||
<div class="setting-block">
|
||
<p style="color:var(--text-secondary);line-height:1.8;">笔临 v1.0.0<br>长篇创作智能协作平台<br><br>专为长篇小说作者打造的跨平台写作系统。<br>本地优先,隐私至上,让AI真正服务于创作。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Focus mode indicator -->
|
||
<div id="focus-indicator">🔴 专注模式 · 按 Esc 退出</div>
|
||
<div id="focusSoundIndicator"><span class="sound-icon">🎵</span> <span id="focusSoundLabel">无音效</span></div>
|
||
|
||
<!-- Volume Context Menu -->
|
||
<div class="ctx-menu" id="volCtxMenu">
|
||
<div class="ctx-item" onclick="enterReadingMode()"><i class="fa-solid fa-book-open"></i> 阅读模式</div>
|
||
<div class="ctx-item" onclick="showToast('重命名分卷')"><i class="fa-solid fa-pen"></i> 重命名</div>
|
||
<div class="ctx-divider"></div>
|
||
<div class="ctx-item" onclick="addChapter()"><i class="fa-solid fa-plus"></i> 新建章节</div>
|
||
<div class="ctx-item" onclick="showToast('导出该卷')"><i class="fa-solid fa-download"></i> 导出该卷</div>
|
||
<div class="ctx-divider"></div>
|
||
<div class="ctx-item" style="color:var(--red);" onclick="showToast('确认删除该分卷?')"><i class="fa-solid fa-trash"></i> 删除分卷</div>
|
||
</div>
|
||
|
||
<!-- Reading Mode Overlay -->
|
||
<div id="reading-mode" role="region" aria-label="连续阅读模式">
|
||
<div class="reading-topbar">
|
||
<button onclick="exitReadingMode()" aria-label="退出">✕ 退出</button><span class="sep"></span>
|
||
<button onclick="setReadingDevice('desktop',this)" class="active">🖥 桌面</button>
|
||
<button onclick="setReadingDevice('tablet',this)">📱 平板</button>
|
||
<button onclick="setReadingDevice('phone',this)">📱 手机</button><span class="sep"></span>
|
||
<button onclick="setReadingFont('serif',this)" class="active">衬线</button>
|
||
<button onclick="setReadingFont('sans',this)">无衬线</button>
|
||
<button onclick="setReadingFont('eyecare',this)">护眼</button><span class="sep"></span>
|
||
<button onclick="toggleTTS()" id="readingTTSBtn">🎧 朗读</button>
|
||
<span style="margin-left:auto;font-size:11px;color:var(--text-muted);">第一卷 · 12章连续</span>
|
||
</div>
|
||
<div class="reading-content" id="readingContent">
|
||
<div class="reading-page"><div class="page-inner"><h2>第一章 · 山村少年</h2><p>青云山脉绵延三千里,在九州大陆的东南角画出一道天然屏障...</p></div></div>
|
||
<div class="reading-page"><div class="page-inner"><h2>第二章 · 意外觉醒</h2><p>林远怎么也没想到,一切会来得如此突然...</p></div></div>
|
||
<div class="reading-page"><div class="page-inner"><h2>第三章 · 入门考验</h2><p>晨光透过稀疏的竹帘,在青石地面上投下细碎的光斑。林远深吸一口气...</p></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== MODALS ==================== -->
|
||
<!-- New Book Modal -->
|
||
<div class="modal-overlay hidden" id="newBookModal">
|
||
<div class="modal" style="max-width:500px;">
|
||
<div class="modal-header"><h3>📖 新建书籍</h3><button class="modal-close" onclick="closeModal('newBookModal')">✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-group"><label>书名</label><input type="text" placeholder="输入书名..." id="newBookName"></div>
|
||
<div class="form-group"><label>分类(可多选)</label><select multiple style="height:80px;" id="newBookCategory"><option>玄幻</option><option>仙侠</option><option>科幻</option><option>言情</option><option>悬疑</option><option>历史</option></select></div>
|
||
<div class="form-group"><label>目标总字数(可选)</label><input type="number" placeholder="如:500000" id="newBookTarget"></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn" onclick="closeModal('newBookModal')">取消</button>
|
||
<button class="btn primary" onclick="createNewBook()">创建书籍</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Version History Modal -->
|
||
<div class="modal-overlay hidden" id="versionModal">
|
||
<div class="modal" style="max-width:900px;">
|
||
<div class="modal-header"><h3>📜 版本历史 — 第3章·入门考验</h3><button class="modal-close" onclick="closeModal('versionModal')">✕</button></div>
|
||
<div class="modal-body">
|
||
<div style="display:flex;gap:12px;margin-bottom:12px;flex-wrap:wrap;">
|
||
<span class="btn" style="font-size:11px;background:var(--bg-active);">全部</span><span class="btn" style="font-size:11px;">自动快照</span><span class="btn" style="font-size:11px;">手动快照</span><span class="btn" style="font-size:11px;">AI生成</span>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:6px;margin-bottom:16px;">
|
||
<div style="display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);cursor:pointer;border:1px solid var(--border);" onclick="showDiffView()">
|
||
<span style="color:var(--accent-light);">📸</span><span style="flex:1;font-weight:600;">手动快照:删男二戏份前</span><span style="font-size:10px;color:var(--text-muted);">2026-06-25 14:32</span><span class="btn" style="font-size:10px;padding:4px 8px;">恢复</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);cursor:pointer;border:1px solid var(--border);">
|
||
<span style="color:var(--text-muted);">⏱</span><span style="flex:1;">自动快照</span><span style="font-size:10px;color:var(--text-muted);">2026-06-25 14:25</span><span class="btn" style="font-size:10px;padding:4px 8px;">恢复</span>
|
||
</div>
|
||
</div>
|
||
<div class="diff-view hidden" id="diffViewArea">
|
||
<div style="font-size:11px;color:var(--text-muted);margin-bottom:4px;">对比:当前版本 ↔ 手动快照</div>
|
||
<div class="diff-pane"><span>晨光透过稀疏的竹帘...</span><span class="diff-del">林远看到男二站在演武场角落。</span><span class="diff-add">林远深吸一口气,推开木门走了出去。</span></div>
|
||
<div class="diff-pane"><span>晨光透过稀疏的竹帘...</span><span>林远看到男二站在演武场角落。</span></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer"><button class="btn" onclick="closeModal('versionModal')">关闭</button><button class="btn primary" onclick="showToast('已创建新快照');closeModal('versionModal')">📸 创建新快照</button></div>
|
||
</div>
|
||
</div>
|
||
<!-- Naming Modal -->
|
||
<div class="modal-overlay hidden" id="namingModal">
|
||
<div class="modal" style="max-width:550px;">
|
||
<div class="modal-header"><h3>🏷 AI命名建议 — 新角色</h3><button class="modal-close" onclick="closeModal('namingModal')">✕</button></div>
|
||
<div class="modal-body">
|
||
<p style="color:var(--text-secondary);margin-bottom:12px;">为新出场的<b>白发长老</b>选择名字:</p>
|
||
<div class="naming-options">
|
||
<div class="naming-opt" onclick="selectName('云逸真人')"><span class="name-cn">云逸真人</span><span class="name-desc">飘逸出尘,暗示其超然地位</span></div>
|
||
<div class="naming-opt" onclick="selectName('玄镜长老')"><span class="name-cn">玄镜长老</span><span class="name-desc">睿智深沉,取"玄妙如镜"</span></div>
|
||
<div class="naming-opt" onclick="selectName('白衍')"><span class="name-cn">白衍</span><span class="name-desc">简洁有力,"衍"暗示传承</span></div>
|
||
<div class="naming-opt" onclick="selectName('苍梧道人')"><span class="name-cn">苍梧道人</span><span class="name-desc">古意盎然,与宗门氛围契合</span></div>
|
||
<div class="naming-opt" onclick="selectName('素问长老')"><span class="name-cn">素问长老</span><span class="name-desc">温和平实,反衬深藏不露</span></div>
|
||
</div>
|
||
<div style="margin-top:12px;display:flex;gap:8px;"><input type="text" placeholder="或自行输入..." style="flex:1;padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg-tertiary);color:#fff;font-size:12px;" id="customNameInput"><button class="btn primary" onclick="selectName(document.getElementById('customNameInput').value||'自定义名称')">确定</button></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Dashboard Modal -->
|
||
<div class="modal-overlay hidden" id="dashboardModal">
|
||
<div class="modal" style="max-width:750px;">
|
||
<div class="modal-header"><h3>📊 驾驶舱 — 仙道长青</h3><button class="modal-close" onclick="closeModal('dashboardModal')">✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="dashboard-grid">
|
||
<div class="dash-card"><div class="dash-num">128,450</div><div class="dash-label">📖 全书总字数</div></div>
|
||
<div class="dash-card"><div class="dash-num">1,280</div><div class="dash-label">⚡ 今日字数</div></div>
|
||
<div class="dash-card"><div class="dash-num">42</div><div class="dash-label">字/分钟 · 当前速度</div></div>
|
||
<div class="dash-card"><div class="dash-num">68天</div><div class="dash-label">📅 预计完本</div></div>
|
||
</div>
|
||
<div style="margin-top:16px;"><h4 style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">📅 写作热力图(近30天)</h4>
|
||
<div style="display:flex;flex-wrap:wrap;gap:2px;" id="heatmapContainer"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toastContainer"></div>
|
||
|
||
<!-- ===== NEW MODALS (V2.0) ===== -->
|
||
<!-- Character Graph -->
|
||
<div class="modal-overlay hidden" id="graphModal"><div class="modal" style="max-width:95vw;width:1100px;">
|
||
<div class="modal-header"><h3>🕸 角色关系图谱 — 仙道长青</h3><button class="modal-close" onclick="closeModal('graphModal')" aria-label="关闭">✕</button></div>
|
||
<div class="modal-body" style="position:relative;">
|
||
<div class="graph-controls">
|
||
<button class="active" onclick="setGraphLayout('cose')">力导向</button>
|
||
<button onclick="setGraphLayout('grid')">网格</button>
|
||
<button onclick="setGraphLayout('circle')">圆形</button>
|
||
<span style="width:1px;height:20px;background:var(--border);margin:0 6px;"></span>
|
||
<button onclick="showToast('添加新角色节点')">+ 添加角色</button>
|
||
<span style="font-size:10px;color:var(--text-muted);margin-left:auto;">节点上限100 · 当前 5</span>
|
||
</div>
|
||
<div id="graphContainer" aria-label="角色关系力导向图"></div>
|
||
<div class="graph-sidebar" id="graphSidebar"><h4 style="margin-bottom:8px;color:#fff;">节点详情</h4><p style="color:var(--text-muted);font-size:11px;">点击节点查看角色详情<br>点击边编辑关系<br>Ctrl+拖拽添加连线</p></div>
|
||
</div>
|
||
</div></div>
|
||
|
||
<!-- Story Timeline -->
|
||
<div class="modal-overlay hidden" id="timelineModal"><div class="modal" style="max-width:1000px;">
|
||
<div class="modal-header"><h3>⏳ 故事时间线 — 仙道长青</h3><button class="modal-close" onclick="closeModal('timelineModal')" aria-label="关闭">✕</button></div>
|
||
<div class="modal-body">
|
||
<div style="display:flex;gap:6px;margin-bottom:12px;flex-wrap:wrap;">
|
||
<button class="btn" style="font-size:10px;background:var(--accent);color:#fff;">全部</button>
|
||
<button class="btn" style="font-size:10px;">按卷</button>
|
||
<button class="btn" style="font-size:10px;">按角色</button>
|
||
<span style="font-size:10px;color:var(--text-muted);margin-left:auto;">粒度:<select style="padding:2px 6px;font-size:10px;background:var(--bg-tertiary);color:#fff;border:1px solid var(--border);border-radius:3px;"><option>按章</option><option>按天</option><option>按月</option></select></span>
|
||
</div>
|
||
<div class="timeline-container">
|
||
<div class="timeline-event" onclick="showToast('跳转到第1章')"><div class="tl-time">大陆历 732年 春 · 第1章</div><div class="tl-title">山村少年 — 林远出场</div><div class="tl-desc">林远在青石村过着平静的猎户生活,偶然发现山崖上的奇异符文。</div><span class="tl-type">📖 章节</span></div>
|
||
<div class="timeline-event" onclick="showToast('跳转到第2章')"><div class="tl-time">大陆历 732年 春 · 第2章(+3天)</div><div class="tl-title">意外觉醒 — 三纹灵根显现</div><div class="tl-desc">林远在危机中觉醒三纹灵根,击败妖兽。</div><span class="tl-type">⚡ 关键事件</span></div>
|
||
<div class="timeline-event conflict" onclick="showToast('跳转到第3章')"><div class="tl-time">大陆历 732年 春 · 第3章(+7天)⚠ 时间冲突</div><div class="tl-title">入门考验 — 测灵石测试</div><div class="tl-desc">林远抵达青云宗。角色年龄计算异常:林远应为16岁,推算显示17岁。</div><span class="tl-type">⚠ 冲突</span></div>
|
||
<div class="timeline-event" onclick="showToast('跳转到第13章')"><div class="tl-time">大陆历 732年 夏 · 第13章(+30天)</div><div class="tl-title">初入宗门 — 拜入青云宗</div><div class="tl-desc">林远正式拜入青云宗外门,结识柳青烟。</div><span class="tl-type">🤝 关系发展</span></div>
|
||
</div>
|
||
</div>
|
||
</div></div>
|
||
|
||
<!-- Export -->
|
||
<div class="modal-overlay hidden" id="exportModal"><div class="modal" style="max-width:500px;">
|
||
<div class="modal-header"><h3>📦 导出 — 第3章·入门考验</h3><button class="modal-close" onclick="closeModal('exportModal')" aria-label="关闭">✕</button></div>
|
||
<div class="modal-body"><div class="export-options">
|
||
<div class="export-opt" onclick="showToast('导出为 .txt');closeModal('exportModal')"><span class="export-icon">📄</span><div class="export-info"><span class="export-name">纯文本 .txt</span><span class="export-desc">无格式纯文本,兼容性最佳</span></div></div>
|
||
<div class="export-opt" onclick="showToast('导出为 .docx');closeModal('exportModal')"><span class="export-icon">📝</span><div class="export-info"><span class="export-name">Word 文档 .docx</span><span class="export-desc">保留格式,自动生成目录</span></div></div>
|
||
<div class="export-opt" onclick="showToast('导出为 .md');closeModal('exportModal')"><span class="export-icon">📋</span><div class="export-info"><span class="export-name">Markdown .md</span><span class="export-desc">适合发布到网络平台</span></div></div>
|
||
<div class="export-opt" onclick="showToast('导出为 PDF (A4模板)');closeModal('exportModal')"><span class="export-icon">📕</span><div class="export-info"><span class="export-name">PDF 文档 .pdf</span><span class="export-desc">A4/信纸排版,页眉含书名+章节名+页码</span></div></div>
|
||
<div class="export-opt" onclick="showToast('打包为 .novel 项目包');closeModal('exportModal')"><span class="export-icon">📦</span><div class="export-info"><span class="export-name">笔临项目包 .novel</span><span class="export-desc">整书数据打包,含版本历史+设定+大纲</span></div></div>
|
||
</div></div>
|
||
</div></div>
|
||
|
||
<!-- Export All -->
|
||
<div class="modal-overlay hidden" id="exportAllModal"><div class="modal" style="max-width:500px;">
|
||
<div class="modal-header"><h3>📦 导出所有书籍</h3><button class="modal-close" onclick="closeModal('exportAllModal')" aria-label="关闭">✕</button></div>
|
||
<div class="modal-body"><p style="color:var(--text-secondary);margin-bottom:12px;">将<b>全部书籍</b>打包为 .novel-all 归档:所有章节版本、大纲、设定、AI对话、知识库、全局设置。</p><p style="font-size:11px;color:var(--orange);">⚠ 预计大小:约 45MB</p><div class="form-group" style="margin-top:12px;"><label>保存路径</label><input type="text" placeholder="选择保存路径..." value="D:/备份/笔临_2026-06-30.novel-all"></div></div>
|
||
<div class="modal-footer"><button class="btn" onclick="closeModal('exportAllModal')">取消</button><button class="btn primary" onclick="showToast('📦 正在打包导出...');closeModal('exportAllModal')">开始导出</button></div>
|
||
</div></div>
|
||
|
||
<!-- Chapter Template -->
|
||
<div class="modal-overlay hidden" id="templateModal"><div class="modal" style="max-width:650px;">
|
||
<div class="modal-header"><h3>📋 模板快速新建章节</h3><button class="modal-close" onclick="closeModal('templateModal')" aria-label="关闭">✕</button></div>
|
||
<div class="modal-body"><div class="template-grid">
|
||
<div class="template-card" onclick="applyTemplate('generic')"><div class="tpl-name">通用模板</div><div class="tpl-preview">## 第{chapterNumber}章 {chapterTitle}<br><br>正文内容...</div><span class="tpl-badge builtin">内置</span></div>
|
||
<div class="template-card" onclick="applyTemplate('webnovel')"><div class="tpl-name">网文常用</div><div class="tpl-preview">## 第{chapterNumber}章 {chapterTitle}<br><br>正文...<br><br>——<br>作者的话:</div><span class="tpl-badge builtin">内置</span></div>
|
||
<div class="template-card" onclick="applyTemplate('custom')"><div class="tpl-name">我的模板</div><div class="tpl-preview">## {volumeName} · 第{chapterNumber}章<br>关联:{outlineItem}<br>出场:{characterList}<br>{penName} | {date}</div><span class="tpl-badge custom">自定义</span></div>
|
||
</div><div class="form-group" style="margin-top:14px;"><label>章节标题</label><input type="text" placeholder="输入章节标题..." id="templateChapterTitle"></div></div>
|
||
<div class="modal-footer"><button class="btn" onclick="closeModal('templateModal')">取消</button><button class="btn primary" onclick="applyTemplate('generic');closeModal('templateModal')">创建章节</button></div>
|
||
</div></div>
|
||
|
||
<!-- Onboarding Wizard -->
|
||
<div class="modal-overlay hidden" id="onboardingModal"><div class="modal" style="max-width:560px;">
|
||
<div class="modal-header"><h3>✒ 欢迎使用笔临</h3></div>
|
||
<div class="modal-body" id="onboardingBody">
|
||
<div class="onboarding-slide" id="obSlide0"><div class="ob-icon">✒</div><h2>欢迎使用笔临</h2><p>你的智能长篇创作伙伴<br>让每一个故事都被认真对待</p></div>
|
||
<div class="onboarding-slide hidden" id="obSlide1"><h2>📑 章节管理与版本历史</h2><p>分卷管理、自动快照、版本对比<br>每一次修改都有迹可循</p></div>
|
||
<div class="onboarding-slide hidden" id="obSlide2"><h2>🤖 AI 辅助写作</h2><p>上下文感知、剧情走向建议、文风学习<br>AI 服务于你的创作</p></div>
|
||
<div class="onboarding-slide hidden" id="obSlide3"><h2>🧠 知识库与伏笔追踪</h2><p>自动抽取角色、地点、伏笔<br>永不遗忘任何细节</p></div>
|
||
<div class="onboarding-slide hidden" id="obSlide4"><h2>设置你的笔名</h2><div class="form-group" style="max-width:300px;margin:16px auto;"><input type="text" placeholder="你的笔名(至少2个字符)" id="obPenName" style="text-align:center;"></div><div style="display:flex;align-items:center;gap:8px;justify-content:center;font-size:12px;color:var(--text-secondary);"><input type="checkbox" id="obCreateSample" checked><label>创建示例书籍"我的第一本书"</label></div></div>
|
||
</div>
|
||
<div class="onboarding-dots" id="obDots"><span class="dot active"></span><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>
|
||
<div class="modal-footer"><button class="btn" id="obSkipBtn" onclick="skipOnboarding()">跳过</button><button class="btn primary" id="obNextBtn" onclick="nextOnboardingStep()">下一步</button></div>
|
||
</div></div>
|
||
|
||
<script>
|
||
// ============ STATE ============
|
||
let openTabs = []; // {id, type:'book'|'settings', name, bookId?}
|
||
let activeTabId = null;
|
||
let isFocusMode = false;
|
||
let wordCount = 2847;
|
||
// V2.0 new state
|
||
let selectedChapters = new Set();
|
||
let currentSound = 'none';
|
||
let isTTSPlaying = false;
|
||
let isSpellCheckOn = false;
|
||
let pomodoroSeconds = 25*60, pomodoroInterval = null, pomodoroRunning = false;
|
||
let onboardingStep = 0, onboardingTotalSlides = 5;
|
||
const books = [
|
||
{ id: 1, name: '仙道长青', category: '玄幻', words: 128450, target: 500000, status: 'ongoing',
|
||
icon: '📖', color: '#7c8cf8', lastEdit: '10分钟前', chapters: 20, vols: 2 },
|
||
{ id: 2, name: '星海迷途', category: '科幻', words: 56200, target: 300000, status: 'draft', icon: '🚀',
|
||
color: '#f0c060', lastEdit: '3小时前', chapters: 8, vols: 1 },
|
||
];
|
||
|
||
function getBook(id) { return books.find(b => b.id === id); }
|
||
|
||
// ============ TAB MANAGEMENT ============
|
||
function openBookTab(bookId) {
|
||
const book = getBook(bookId);
|
||
if (!book) return;
|
||
const existing = openTabs.find(t => t.type === 'book' && t.bookId === bookId);
|
||
if (existing) {
|
||
switchTab(existing.id);
|
||
return;
|
||
}
|
||
const tabId = 'tab_' + Date.now();
|
||
openTabs.push({ id: tabId, type: 'book', name: book.name, bookId: bookId });
|
||
activeTabId = tabId;
|
||
renderTabs();
|
||
showEditorLayout(book);
|
||
}
|
||
|
||
function openSettingsTab() {
|
||
const existing = openTabs.find(t => t.type === 'settings');
|
||
if (existing) { switchTab(existing.id); return; }
|
||
const tabId = 'tab_settings';
|
||
openTabs.push({ id: tabId, type: 'settings', name: '系统设置' });
|
||
activeTabId = tabId;
|
||
renderTabs();
|
||
showSettingsPage();
|
||
}
|
||
|
||
function switchTab(tabId) {
|
||
activeTabId = tabId;
|
||
renderTabs();
|
||
const tab = openTabs.find(t => t.id === tabId);
|
||
if (!tab) return;
|
||
if (tab.type === 'book') {
|
||
showEditorLayout(getBook(tab.bookId));
|
||
} else if (tab.type === 'settings') {
|
||
showSettingsPage();
|
||
}
|
||
}
|
||
|
||
function closeTab(tabId, e) {
|
||
e.stopPropagation();
|
||
const idx = openTabs.findIndex(t => t.id === tabId);
|
||
if (idx === -1) return;
|
||
const closing = openTabs[idx];
|
||
openTabs.splice(idx, 1);
|
||
if (activeTabId === tabId) {
|
||
if (openTabs.length > 0) {
|
||
activeTabId = openTabs[Math.max(0, idx - 1)].id;
|
||
switchTab(activeTabId);
|
||
} else {
|
||
activeTabId = null;
|
||
renderTabs();
|
||
goHome();
|
||
return;
|
||
}
|
||
}
|
||
renderTabs();
|
||
}
|
||
|
||
function renderTabs() {
|
||
const container = document.getElementById('tabContainer');
|
||
container.innerHTML = openTabs.map(t => {
|
||
const isActive = t.id === activeTabId;
|
||
const dotColor = t.type === 'settings' ? '#aaa' : (getBook(t.bookId)?.color || '#7c8cf8');
|
||
return `<div class="tab ${isActive?'active':''}" onclick="switchTab('${t.id}')">
|
||
<span class="dot" style="background:${dotColor};"></span>
|
||
<span class="tab-label">${t.name}</span>
|
||
<span class="close-tab" onclick="closeTab('${t.id}',event)"><i class="fa-solid fa-xmark"></i></span>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// ============ VIEW SWITCHING ============
|
||
function goHome() {
|
||
document.getElementById('home-page').classList.remove('hidden');
|
||
document.getElementById('editor-layout').classList.add('hidden');
|
||
document.getElementById('settings-page').classList.add('hidden');
|
||
openTabs = [];
|
||
activeTabId = null;
|
||
renderTabs();
|
||
renderBookGrid();
|
||
}
|
||
|
||
function showEditorLayout(book) {
|
||
document.getElementById('home-page').classList.add('hidden');
|
||
document.getElementById('editor-layout').classList.remove('hidden');
|
||
document.getElementById('settings-page').classList.add('hidden');
|
||
document.getElementById('sidebarBookTitle').textContent = book.icon + ' ' + book.name;
|
||
const badge = document.getElementById('sidebarBookBadge');
|
||
if (book.status === 'ongoing') { badge.textContent = '连载中';
|
||
badge.style.background = 'var(--accent)'; } else if (book.status === 'draft') { badge.textContent =
|
||
'草稿';
|
||
badge.style.background = 'var(--orange)'; } else { badge.textContent = '已完成';
|
||
badge.style.background = 'var(--green)'; }
|
||
updateWordCount();
|
||
}
|
||
|
||
function showSettingsPage() {
|
||
document.getElementById('home-page').classList.add('hidden');
|
||
document.getElementById('editor-layout').classList.add('hidden');
|
||
document.getElementById('settings-page').classList.remove('hidden');
|
||
}
|
||
|
||
// ============ BOOK GRID ============
|
||
function renderBookGrid() {
|
||
const grid = document.getElementById('bookGrid');
|
||
grid.innerHTML = books.map(b => {
|
||
const progress = b.target ? Math.round((b.words / b.target) * 100) : 0;
|
||
const badgeClass = b.status === 'ongoing' ? 'badge-ongoing' : b.status === 'draft' ?
|
||
'badge-draft' : 'badge-done';
|
||
const badgeText = b.status === 'ongoing' ? '连载中' : b.status === 'draft' ? '草稿' : '已完成';
|
||
return `
|
||
<div class="book-card" onclick="openBookTab(${b.id})">
|
||
<div class="book-cover-icon" style="background:${b.color}22;color:${b.color};">${b.icon}</div>
|
||
<div class="book-name">${b.name}</div>
|
||
<div class="book-meta">
|
||
<span>📂 ${b.category}</span><span>📝 ${b.words.toLocaleString()}字</span><br>
|
||
<span>📑 ${b.chapters}章</span><span>🕐 ${b.lastEdit}</span>
|
||
</div>
|
||
<div class="book-progress"><div class="book-progress-fill" style="width:${progress}%;background:${b.color};"></div></div>
|
||
<span class="book-badge ${badgeClass}">${badgeText}</span>
|
||
</div>`;
|
||
}).join('') + `
|
||
<div class="book-card book-card-placeholder" onclick="showNewBookModal()">
|
||
<span class="plus-icon"><i class="fa-solid fa-plus"></i></span>
|
||
<span>创建新书籍</span>
|
||
</div>`;
|
||
document.getElementById('bookCount').textContent = books.length + '本';
|
||
// 最近编辑
|
||
const recentList = document.getElementById('recentList');
|
||
recentList.innerHTML = books.slice(0, 4).map(b =>
|
||
`<span style="padding:6px 12px;background:var(--bg-tertiary);border-radius:16px;font-size:11px;cursor:pointer;color:var(--text-secondary);" onclick="openBookTab(${b.id})">${b.icon} ${b.name} · ${b.lastEdit}</span>`
|
||
).join('');
|
||
}
|
||
|
||
function showNewBookModal() { document.getElementById('newBookModal').classList.remove('hidden'); }
|
||
|
||
function createNewBook() {
|
||
const name = document.getElementById('newBookName').value.trim();
|
||
if (!name) { showToast('请输入书名'); return; }
|
||
const category = document.getElementById('newBookCategory').selectedOptions[0]?.value || '未分类';
|
||
const target = parseInt(document.getElementById('newBookTarget').value) || 300000;
|
||
const newBook = { id: Date.now(), name, category, words: 0, target, status: 'draft', icon: '📕',
|
||
color: '#7c8cf8', lastEdit: '刚刚', chapters: 0, vols: 1 };
|
||
books.unshift(newBook);
|
||
closeModal('newBookModal');
|
||
renderBookGrid();
|
||
openBookTab(newBook.id);
|
||
showToast('✅ 书籍已创建:' + name);
|
||
}
|
||
|
||
function showImportModal() { showToast('📥 导入功能:支持.txt/.docx/.md及.novel项目包。大于20MB会提示确认。'); }
|
||
|
||
// ============ SIDEBAR ============
|
||
function switchSidebarPanel(panelId, btn) {
|
||
document.querySelectorAll('#left-sidebar .sidebar-panel').forEach(p => p.classList.remove('active'));
|
||
document.querySelectorAll('#left-sidebar .nav-btn').forEach(b => b.classList.remove('active'));
|
||
document.getElementById(panelId).classList.add('active');
|
||
btn.classList.add('active');
|
||
}
|
||
|
||
function toggleVol(header) {
|
||
header.classList.toggle('open');
|
||
header.nextElementSibling.classList.toggle('hidden');
|
||
}
|
||
|
||
function selectChapter(el, chNum) {
|
||
document.querySelectorAll('.chapter-item').forEach(i => i.classList.remove('active'));
|
||
el.classList.add('active');
|
||
document.querySelector('#editor-toolbar .ch-label').textContent = `当前:第${chNum}章 · ${el.querySelector('.ch-title').textContent}`;
|
||
wordCount = Math.floor(Math.random() * 3000) + 1500;
|
||
updateWordCount();
|
||
showToast(`已切换到第${chNum}章`);
|
||
}
|
||
|
||
function addChapter() {
|
||
const tree = document.getElementById('chapter-tree');
|
||
const volChapters = tree.querySelector('.vol-chapters:not(.hidden)') || tree.querySelector('.vol-chapters');
|
||
const chNum = volChapters.querySelectorAll('.chapter-item').length + 1;
|
||
const newCh = document.createElement('div');
|
||
newCh.className = 'chapter-item';
|
||
newCh.setAttribute('data-ch', chNum);
|
||
newCh.onclick = function() { selectChapter(this, chNum); };
|
||
newCh.innerHTML =
|
||
`<span class="ch-num">第${chNum}章</span><span class="ch-title">新章节</span><span class="ch-badge draft">草稿</span>`;
|
||
volChapters.appendChild(newCh);
|
||
showToast(`已创建第${chNum}章`);
|
||
selectChapter(newCh, chNum);
|
||
}
|
||
|
||
function addVolume() {
|
||
const tree = document.getElementById('chapter-tree');
|
||
const volNum = tree.querySelectorAll('.vol-group').length + 1;
|
||
const volGroup = document.createElement('div');
|
||
volGroup.className = 'vol-group';
|
||
volGroup.innerHTML = `<div class="vol-header open" onclick="toggleVol(this)"><span class="vol-arrow">▶</span> 第${volNum}卷·未命名<span class="vol-count">0章 · 0字</span></div><div class="vol-chapters"></div>`;
|
||
tree.appendChild(volGroup);
|
||
showToast(`已创建第${volNum}卷`);
|
||
}
|
||
|
||
// ============ RIGHT PANEL ============
|
||
function switchRightPanel(panelId, btn) {
|
||
document.querySelectorAll('#right-panel .panel-content').forEach(p => p.classList.remove('active'));
|
||
document.querySelectorAll('#right-panel .panel-tab').forEach(b => b.classList.remove('active'));
|
||
document.getElementById(panelId).classList.add('active');
|
||
btn.classList.add('active');
|
||
}
|
||
|
||
// ============ SETTINGS PAGE NAV ============
|
||
function switchSettingsNav(section, btn) {
|
||
document.querySelectorAll('.settings-sidebar .settings-nav-item').forEach(i => i.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.querySelectorAll('#settingsContent .settings-section').forEach(s => s.classList.add('hidden'));
|
||
const target = document.getElementById('settings-' + section);
|
||
if (target) target.classList.remove('hidden');
|
||
}
|
||
|
||
// ============ AI CHAT ============
|
||
function sendMessage() {
|
||
const input = document.getElementById('chatInput');
|
||
const msg = input.value.trim();
|
||
if (!msg) return;
|
||
const chatMessages = document.getElementById('chatMessages');
|
||
const userMsg = document.createElement('div');
|
||
userMsg.className = 'chat-msg user';
|
||
userMsg.textContent = msg;
|
||
chatMessages.appendChild(userMsg);
|
||
input.value = '';
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
setTimeout(() => {
|
||
const aiMsg = document.createElement('div');
|
||
aiMsg.className = 'chat-msg ai';
|
||
if (msg.includes('续写') || msg.includes('继续')) {
|
||
aiMsg.innerHTML =
|
||
`<b>笔临AI</b> 正在续写...<br><br>林远收回手掌,测灵石上的光芒渐渐暗淡。周围少年的目光已经从轻视变成了复杂的打量。白发长老缓步上前,枯瘦的手指轻轻点了点他的眉心。"孩子,跟我来。"`;
|
||
} else {
|
||
aiMsg.innerHTML =
|
||
`<b>笔临AI</b> 收到。基于当前上下文,需要我提供接下来的剧情走向吗?<div class="plot-options"><div class="plot-opt" onclick="selectPlot('A')"><span class="opt-label">走向A</span> — 林远被长老单独召见。</div><div class="plot-opt" onclick="selectPlot('B')"><span class="opt-label">走向B</span> — 同门弟子开始拉拢林远。</div></div>`;
|
||
}
|
||
chatMessages.appendChild(aiMsg);
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}, 600);
|
||
}
|
||
|
||
function quickCmd(cmd) { document.getElementById('chatInput').value = cmd + ' ';
|
||
document.getElementById('chatInput').focus(); }
|
||
|
||
function selectPlot(plotId) {
|
||
showToast(`已选择剧情走向 ${plotId},AI正在生成场景...`);
|
||
const chatMessages = document.getElementById('chatMessages');
|
||
const aiMsg = document.createElement('div');
|
||
aiMsg.className = 'chat-msg ai';
|
||
aiMsg.innerHTML =
|
||
`<b>笔临AI</b> 根据走向${plotId}生成场景:<br><br>白发长老领着林远穿过演武场侧门,走入一条幽深的长廊。"你的三纹灵根,"长老忽然停下脚步,"与百年前那位叛门而去的天才,一模一样。"`;
|
||
chatMessages.appendChild(aiMsg);
|
||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||
}
|
||
|
||
// ============ FOCUS MODE ============
|
||
function toggleFocusMode() {
|
||
isFocusMode = !isFocusMode;
|
||
const app = document.getElementById('app');
|
||
if (isFocusMode) { app.classList.add('focus-mode');
|
||
document.getElementById('editor-content').focus();
|
||
showToast('进入专注模式 · 按Esc退出'); } else { app.classList.remove('focus-mode');
|
||
showToast('已退出专注模式'); }
|
||
}
|
||
|
||
function saveSnapshot() { const name = prompt('为快照命名(可选):', '手动快照 ' + new Date()
|
||
.toLocaleTimeString()); if (name !== null) showToast('📸 快照已保存:' + (name || '未命名')); }
|
||
|
||
function updateWordCount() {
|
||
const content = document.getElementById('editor-content').innerText;
|
||
wordCount = content.replace(/\s/g, '').length;
|
||
const display = document.getElementById('wordCountDisplay');
|
||
display.textContent = wordCount.toLocaleString() + ' 字';
|
||
display.classList.toggle('warn', wordCount >= 3000);
|
||
document.querySelector('#editor-statusbar span:nth-child(2)').innerHTML =
|
||
`📖 本章:<b>${wordCount.toLocaleString()}</b> / 提醒 3,000`;
|
||
}
|
||
|
||
// ============ MODALS ============
|
||
function showVersionModal() { document.getElementById('versionModal').classList.remove('hidden'); }
|
||
|
||
function showNamingModal() { document.getElementById('namingModal').classList.remove('hidden'); }
|
||
|
||
function showDashboardModal() {
|
||
document.getElementById('dashboardModal').classList.remove('hidden');
|
||
const container = document.getElementById('heatmapContainer');
|
||
container.innerHTML = Array.from({ length: 30 }, (_, i) => {
|
||
const cls = Math.random() > 0.35 ? (Math.random() > 0.5 ? 'filled' : 'high') : '';
|
||
return `<span class="heatmap-cell ${cls}" title="6月${i+1}日"></span>`;
|
||
}).join('');
|
||
}
|
||
|
||
function closeModal(id) { document.getElementById(id).classList.add('hidden'); if (id === 'versionModal')
|
||
document.getElementById('diffViewArea')?.classList.add('hidden'); }
|
||
|
||
function showDiffView() { document.getElementById('diffViewArea').classList.remove('hidden');
|
||
document.getElementById('diffViewArea').scrollIntoView({ behavior: 'smooth' }); }
|
||
|
||
function selectName(name) { showToast(`✅ 已选择命名:${name}`);
|
||
closeModal('namingModal'); }
|
||
|
||
function showToast(msg) {
|
||
const container = document.getElementById('toastContainer');
|
||
const toast = document.createElement('div');
|
||
toast.className = 'toast';
|
||
toast.textContent = msg;
|
||
container.appendChild(toast);
|
||
setTimeout(() => toast.remove(), 2500);
|
||
}
|
||
|
||
// ============ GLOBAL SHORTCUTS ============
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
if (isFocusMode) toggleFocusMode();
|
||
if (document.getElementById('reading-mode').classList.contains('active')) exitReadingMode();
|
||
closeAllCtxMenus();
|
||
}
|
||
if (e.ctrlKey && e.key === 's') { e.preventDefault(); showToast('💾 已保存'); }
|
||
if (e.ctrlKey && e.key === 'k') { e.preventDefault(); document.getElementById('chatInput').focus(); }
|
||
if (e.ctrlKey && e.altKey && e.key === 'f') { e.preventDefault(); toggleFocusMode(); }
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'R') { e.preventDefault(); toggleTTS(); }
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'N') { e.preventDefault(); addChapter(); }
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'I') { e.preventDefault(); showToast('💡 灵感快速捕获窗已打开'); }
|
||
});
|
||
|
||
// ============ V2.0 NEW FUNCTIONS ============
|
||
// Theme
|
||
function switchTheme(theme) {
|
||
document.documentElement.removeAttribute('data-theme');
|
||
if (theme !== 'default') document.documentElement.setAttribute('data-theme', theme);
|
||
localStorage.setItem('bilin-theme', theme);
|
||
}
|
||
(function() {
|
||
const t = localStorage.getItem('bilin-theme');
|
||
if (t && t !== 'default') document.documentElement.setAttribute('data-theme', t);
|
||
const sel = document.getElementById('themeSelect');
|
||
if (sel && t) sel.value = t;
|
||
})();
|
||
|
||
// Batch selection
|
||
function toggleChapterSelect(el, chNum) {
|
||
el.classList.toggle('checked');
|
||
const item = el.parentElement;
|
||
if (el.classList.contains('checked')) { selectedChapters.add(chNum); item.classList.add('selected'); }
|
||
else { selectedChapters.delete(chNum); item.classList.remove('selected'); }
|
||
updateBatchToolbar();
|
||
}
|
||
function updateBatchToolbar() {
|
||
const tb = document.getElementById('batchToolbar');
|
||
if (selectedChapters.size > 0) { tb.classList.add('show'); document.getElementById('batchCount').textContent = '已选 ' + selectedChapters.size + ' 章'; }
|
||
else tb.classList.remove('show');
|
||
}
|
||
function clearBatchSelection() {
|
||
selectedChapters.clear();
|
||
document.querySelectorAll('.chapter-item').forEach(i => { i.classList.remove('selected'); const s = i.querySelector('.ch-select'); if (s) s.classList.remove('checked'); });
|
||
updateBatchToolbar(); showToast('已取消选择');
|
||
}
|
||
function batchSetStatus(status) { showToast('已批量设为 ' + status + ':' + selectedChapters.size + ' 章'); clearBatchSelection(); }
|
||
function batchExport() { showToast('批量导出 ' + selectedChapters.size + ' 章...'); clearBatchSelection(); }
|
||
|
||
// TTS
|
||
function toggleTTS() {
|
||
isTTSPlaying = !isTTSPlaying;
|
||
const btn = document.getElementById('btnTTS'), rbtn = document.getElementById('readingTTSBtn');
|
||
if (isTTSPlaying) { if (btn) btn.classList.add('tts-playing'); if (rbtn) rbtn.classList.add('tts-playing'); showToast('🔊 开始朗读...'); }
|
||
else { if (btn) btn.classList.remove('tts-playing'); if (rbtn) rbtn.classList.remove('tts-playing'); showToast('🔇 朗读已暂停'); }
|
||
}
|
||
|
||
// Spell check
|
||
function toggleSpellCheck() {
|
||
isSpellCheckOn = !isSpellCheckOn;
|
||
const btn = document.getElementById('btnSpellCheck');
|
||
if (isSpellCheckOn) { btn.classList.add('active'); showToast('✅ 拼写检查已开启'); }
|
||
else { btn.classList.remove('active'); showToast('拼写检查已关闭'); }
|
||
}
|
||
|
||
// Pomodoro
|
||
function togglePomodoro() { if (pomodoroRunning) { stopPomodoro(); return; } pomodoroRunning = true; pomodoroSeconds = 25*60; document.getElementById('pomodoroTimer').classList.add('running'); updatePomodoroDisplay(); showToast('🍅 番茄钟开始 · 25分钟'); pomodoroInterval = setInterval(function() { pomodoroSeconds--; updatePomodoroDisplay(); if (pomodoroSeconds <= 0) { stopPomodoro(); showToast('🍅 番茄钟完成!新增 320 字'); } }, 1000); }
|
||
function stopPomodoro() { pomodoroRunning = false; clearInterval(pomodoroInterval); document.getElementById('pomodoroTimer').classList.remove('running'); updatePomodoroDisplay(); }
|
||
function updatePomodoroDisplay() { var m = Math.floor(pomodoroSeconds / 60), s = pomodoroSeconds % 60; document.getElementById('pomodoroTimer').innerHTML = '🍅 ' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0'); }
|
||
|
||
// Sound
|
||
function toggleSoundPopover() { document.getElementById('soundPopover').classList.toggle('show'); }
|
||
function selectSound(sound, el) {
|
||
currentSound = sound;
|
||
document.querySelectorAll('.sound-item').forEach(function(i) { i.classList.remove('active'); });
|
||
el.classList.add('active'); document.getElementById('soundPopover').classList.remove('show');
|
||
var label = el.textContent.trim().split('\n')[0].trim();
|
||
document.getElementById('focusSoundLabel').textContent = sound === 'none' ? '无音效' : label;
|
||
showToast(sound === 'none' ? '🔇 音效已关闭' : '🎵 已选择音效:' + label);
|
||
}
|
||
document.addEventListener('click', function(e) {
|
||
var pop = document.getElementById('soundPopover'), btn = document.getElementById('btnSound');
|
||
if (pop && !pop.contains(e.target) && e.target !== btn && !btn.contains(e.target)) pop.classList.remove('show');
|
||
});
|
||
|
||
// Reading mode
|
||
function enterReadingMode() { document.getElementById('reading-mode').classList.add('active'); document.getElementById('readingContent').scrollTop = 0; closeAllCtxMenus(); showToast('进入阅读模式 · 按Esc或点击退出'); }
|
||
function exitReadingMode() { document.getElementById('reading-mode').classList.remove('active'); }
|
||
function setReadingDevice(device, btn) { var rm = document.getElementById('reading-mode'); rm.classList.remove('device-phone', 'device-tablet'); if (device !== 'desktop') rm.classList.add('device-' + device); btn.parentElement.querySelectorAll('button').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); }
|
||
function setReadingFont(font, btn) { var rm = document.getElementById('reading-mode'); rm.classList.remove('font-serif', 'font-sans', 'font-eyecare'); rm.classList.add('font-' + font); btn.parentElement.querySelectorAll('button').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); }
|
||
|
||
// Volume context menu
|
||
function showVolContextMenu(e, header) { e.preventDefault(); var menu = document.getElementById('volCtxMenu'); menu.style.left = e.clientX + 'px'; menu.style.top = e.clientY + 'px'; menu.classList.add('show'); }
|
||
function closeAllCtxMenus() { document.getElementById('volCtxMenu').classList.remove('show'); }
|
||
document.addEventListener('click', function(e) { if (!e.target.closest('.ctx-menu') && !e.target.closest('.vol-menu-btn')) closeAllCtxMenus(); });
|
||
|
||
// Character graph (Cytoscape.js)
|
||
var cyInstance = null;
|
||
function showGraphModal() { document.getElementById('graphModal').classList.remove('hidden'); setTimeout(initGraph, 300); }
|
||
function initGraph() {
|
||
if (cyInstance) { cyInstance.resize(); return; }
|
||
var container = document.getElementById('graphContainer');
|
||
if (!container || typeof cytoscape === 'undefined') return;
|
||
cyInstance = cytoscape({
|
||
container: container,
|
||
style: [
|
||
{ selector: 'node', style: { 'background-color': '#6c63ff', 'label': 'data(label)', 'color': '#e0e0e0', 'font-size': '12px', 'text-valign': 'center', 'text-halign': 'center', 'width': 50, 'height': 50, 'border-width': 2, 'border-color': '#9da8fa' } },
|
||
{ selector: 'edge', style: { 'width': 2, 'line-color': '#4ea8de', 'target-arrow-color': '#4ea8de', 'target-arrow-shape': 'triangle', 'curve-style': 'bezier', 'label': 'data(label)', 'color': '#b0b0b8', 'font-size': '10px' } },
|
||
{ selector: '.high', style: { 'line-color': '#e0556a', 'target-arrow-color': '#e0556a', 'width': 4 } }
|
||
],
|
||
elements: [
|
||
{ data: { id: 'linyuan', label: '林远' } }, { data: { id: 'zhanglao', label: '白发长老' } },
|
||
{ data: { id: 'liuqingyan', label: '柳青烟' } }, { data: { id: 'zongmen', label: '青云宗' } },
|
||
{ data: { id: 'shixiong', label: '大师兄' } },
|
||
{ data: { id: 'e1', source: 'linyuan', target: 'zhanglao', label: '注意 (3)' } },
|
||
{ data: { id: 'e2', source: 'linyuan', target: 'liuqingyan', label: '暗恋 (4)' }, classes: 'high' },
|
||
{ data: { id: 'e3', source: 'linyuan', target: 'zongmen', label: '归属 (2)' } },
|
||
{ data: { id: 'e4', source: 'shixiong', target: 'linyuan', label: '嫉妒 (1)' } },
|
||
{ data: { id: 'e5', source: 'zhanglao', target: 'zongmen', label: '执掌 (3)' } }
|
||
],
|
||
layout: { name: 'cose-bilkent', animate: true, animationDuration: 1000 }
|
||
});
|
||
cyInstance.on('tap', 'node', function(evt) { document.getElementById('graphSidebar').classList.add('show'); document.getElementById('graphSidebar').querySelector('h4').textContent = '角色:' + evt.target.data('label'); });
|
||
cyInstance.on('tap', function(evt) { if (evt.target === cyInstance) document.getElementById('graphSidebar').classList.remove('show'); });
|
||
}
|
||
function setGraphLayout(layout) {
|
||
if (!cyInstance) return;
|
||
document.querySelectorAll('#graphModal .graph-controls button').forEach(function(b, i) { if (i < 3) b.classList.remove('active'); });
|
||
event.target.classList.add('active');
|
||
var layouts = { grid: { name: 'grid' }, circle: { name: 'circle' }, cose: { name: 'cose-bilkent', animate: true } };
|
||
cyInstance.layout(layouts[layout] || layouts.cose).run();
|
||
}
|
||
|
||
// Modals (new)
|
||
function showTimelineModal() { document.getElementById('timelineModal').classList.remove('hidden'); }
|
||
function showExportModal() { document.getElementById('exportModal').classList.remove('hidden'); }
|
||
function showExportAllModal() { document.getElementById('exportAllModal').classList.remove('hidden'); }
|
||
function showTemplateModal() { document.getElementById('templateModal').classList.remove('hidden'); }
|
||
function applyTemplate(type) { var title = document.getElementById('templateChapterTitle').value || '新章节'; showToast('📋 使用模板创建章节:' + title); addChapter(); }
|
||
function showWordFreqInPanel() { var tabBtn = document.querySelector('#right-panel .panel-tab:nth-child(3)'); if (tabBtn) switchRightPanel('wordfreq', tabBtn); }
|
||
function recordShortcut(el) { el.classList.add('recording'); el.textContent = '按下组合键...'; setTimeout(function() { el.classList.remove('recording'); el.textContent = 'Ctrl+N'; showToast('快捷键已录制'); }, 2000); }
|
||
|
||
// Onboarding
|
||
function showOnboarding() { onboardingStep = 0; document.getElementById('onboardingModal').classList.remove('hidden'); updateOnboardingSlide(); }
|
||
function updateOnboardingSlide() {
|
||
for (var i = 0; i < onboardingTotalSlides; i++) { var slide = document.getElementById('obSlide' + i); if (slide) slide.classList.toggle('hidden', i !== onboardingStep); }
|
||
document.querySelectorAll('#obDots .dot').forEach(function(d, i) { d.classList.toggle('active', i === onboardingStep); });
|
||
var nextBtn = document.getElementById('obNextBtn'), skipBtn = document.getElementById('obSkipBtn');
|
||
if (onboardingStep === onboardingTotalSlides - 1) { nextBtn.textContent = '开始创作'; skipBtn.style.display = 'none'; }
|
||
else { nextBtn.textContent = '下一步'; skipBtn.style.display = 'inline-block'; }
|
||
}
|
||
function nextOnboardingStep() { if (onboardingStep < onboardingTotalSlides - 1) { onboardingStep++; updateOnboardingSlide(); } else completeOnboarding(); }
|
||
function skipOnboarding() { completeOnboarding(); }
|
||
function completeOnboarding() {
|
||
var penName = (document.getElementById('obPenName') && document.getElementById('obPenName').value.trim()) || '未命名作者';
|
||
var createSample = document.getElementById('obCreateSample') && document.getElementById('obCreateSample').checked;
|
||
closeModal('onboardingModal'); showToast('🎉 欢迎,' + penName + '!开始你的创作之旅吧!');
|
||
if (createSample) { var b = { id: Date.now(), name: '我的第一本书', category: '未分类', words: 0, target: 100000, status: 'draft', icon: '📕', color: '#7c8cf8', lastEdit: '刚刚', chapters: 0, vols: 1 }; books.unshift(b); renderBookGrid(); }
|
||
}
|
||
|
||
// Update selectChapter for batch
|
||
var origSelectChapter = selectChapter;
|
||
selectChapter = function(el, chNum) {
|
||
if (event && (event.ctrlKey || event.metaKey || event.shiftKey)) { toggleChapterSelect(el.querySelector('.ch-select'), chNum); return; }
|
||
origSelectChapter(el, chNum);
|
||
};
|
||
|
||
// Update updateWordCount for daily goal
|
||
var origUpdateWordCount = updateWordCount;
|
||
updateWordCount = function() {
|
||
origUpdateWordCount();
|
||
var goalFill = document.getElementById('goalFill'), goalLabel = document.getElementById('goalLabel');
|
||
if (!goalFill || !goalLabel) return;
|
||
var dailyGoal = 3000, todayWords = 1280 + Math.floor(Math.random() * 200);
|
||
var pct = Math.min(100, Math.round((todayWords / dailyGoal) * 100));
|
||
goalFill.style.width = pct + '%';
|
||
goalFill.classList.toggle('warn', pct >= 80 && pct < 100);
|
||
goalFill.classList.toggle('done', pct >= 100);
|
||
goalLabel.textContent = todayWords.toLocaleString() + '/' + dailyGoal.toLocaleString();
|
||
};
|
||
|
||
// Override closeModal for graph cleanup
|
||
var origCloseModal = closeModal;
|
||
closeModal = function(id) {
|
||
origCloseModal(id);
|
||
if (id === 'graphModal' && cyInstance) { cyInstance.destroy(); cyInstance = null; }
|
||
};
|
||
document.querySelectorAll('.modal-overlay').forEach(overlay => {
|
||
overlay.addEventListener('click', function(e) { if (e.target === this) { this.classList.add('hidden'); if (
|
||
this.id === 'versionModal') document.getElementById('diffViewArea')?.classList.add(
|
||
'hidden'); } });
|
||
});
|
||
|
||
// ============ INIT ============
|
||
renderBookGrid();
|
||
updateWordCount();
|
||
document.getElementById('editor-content').addEventListener('focus', function() {
|
||
if (this.textContent.trim() === '') this.innerHTML = '<span class="placeholder">开始书写你的故事...</span>';
|
||
});
|
||
document.getElementById('editor-content').addEventListener('blur', function() {
|
||
if (this.textContent.trim() === '') this.innerHTML = '<span class="placeholder">开始书写你的故事...</span>';
|
||
});
|
||
console.log('✒ 笔临 - 长篇创作智能协作平台 已就绪');
|
||
console.log(' 文人雅韵七色:墨砚 | 竹语 | 月夜 | 宣纸 | 晨雾 | 茶园 | 暮光');
|
||
|
||
// Show onboarding on first visit
|
||
if (!localStorage.getItem('bilin-onboarded')) { setTimeout(showOnboarding, 600); localStorage.setItem('bilin-onboarded', '1'); }
|
||
// 窗口控制按钮(模拟效果 + 实际浏览器窗口控制提示)
|
||
document.getElementById('btn-minimize').addEventListener('click', () => {
|
||
// Web应用中无法真正最小化,可提示或模拟
|
||
showToast('最小化功能需在客户端环境中使用');
|
||
});
|
||
document.getElementById('btn-maximize').addEventListener('click', () => {
|
||
if (!document.fullscreenElement) {
|
||
document.documentElement.requestFullscreen().catch(() => {
|
||
showToast('全屏切换失败,请使用浏览器快捷键');
|
||
});
|
||
} else {
|
||
document.exitFullscreen();
|
||
}
|
||
});
|
||
document.getElementById('btn-close').addEventListener('click', () => {
|
||
// 浏览器中 window.close() 通常只能关闭由脚本打开的窗口
|
||
window.close();
|
||
// 如果无法关闭,给出提示
|
||
setTimeout(() => {
|
||
showToast('请使用 Ctrl+W 或浏览器关闭按钮退出');
|
||
}, 100);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |