Bladeren bron

修改按钮到侧边栏

sequoia00 1 maand geleden
bovenliggende
commit
ae7fd89a76
1 gewijzigde bestanden met toevoegingen van 561 en 137 verwijderingen
  1. 561 137
      static/web/viewer.html

+ 561 - 137
static/web/viewer.html

@@ -106,22 +106,309 @@
             background-color: #1f2937;
             color: #f9fafb;
         }
-
-        /* 底部按钮栏 */
-        .bottom-bar {
-            position: fixed;
-            bottom: 0;
-            margin: auto;
-            width: 75%;
-            left: 50%;
-            transform: translateX(-50%);
-            background-color: rgba(255, 255, 255, 0.0);
-            display: flex;
-            justify-content: space-around;
-            padding: 6px 0;
-            box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
-            z-index: 1000;
-        }
+
+        :root {
+            --reader-controls-size: 52px;
+            --reader-controls-gap: 8px;
+            --reader-controls-radius: 18px;
+            --reader-controls-padding: 10px;
+            --reader-controls-surface: rgba(255, 255, 255, 0.96);
+            --reader-controls-border: rgba(15, 23, 42, 0.08);
+            --reader-controls-icon: 20px;
+            --reader-controls-font: 11px;
+            --reader-controls-label: none;
+        }
+
+        body.reader-size-medium {
+            --reader-controls-size: 60px;
+            --reader-controls-gap: 10px;
+            --reader-controls-radius: 20px;
+            --reader-controls-padding: 12px;
+            --reader-controls-icon: 22px;
+            --reader-controls-font: 12px;
+        }
+
+        body.reader-size-large {
+            --reader-controls-size: 68px;
+            --reader-controls-gap: 12px;
+            --reader-controls-radius: 22px;
+            --reader-controls-padding: 14px;
+            --reader-controls-icon: 24px;
+            --reader-controls-font: 13px;
+        }
+
+        #mainContainer {
+            display: grid;
+            grid-template-columns: minmax(0, 1fr);
+            grid-template-rows: auto minmax(0, 1fr);
+            min-height: 100%;
+        }
+
+        #readerWorkspace {
+            display: grid;
+            grid-template-columns: auto minmax(0, 1fr);
+            grid-template-rows: minmax(0, 1fr);
+            align-items: stretch;
+            min-height: 0;
+            min-width: 0;
+            background: linear-gradient(180deg, rgba(248, 250, 252, 0.9), rgba(241, 245, 249, 0.72));
+        }
+
+        #viewerContainer {
+            min-width: 0;
+            min-height: 0;
+        }
+
+        body.reader-position-right #readerWorkspace {
+            grid-template-columns: minmax(0, 1fr) auto;
+        }
+
+        body.reader-position-top #readerWorkspace,
+        body.reader-position-bottom #readerWorkspace {
+            grid-template-columns: minmax(0, 1fr);
+            grid-template-rows: auto minmax(0, 1fr);
+        }
+
+        body.reader-position-bottom #readerWorkspace {
+            grid-template-rows: minmax(0, 1fr) auto;
+        }
+
+        #readerControls {
+            display: flex;
+            gap: var(--reader-controls-gap);
+            padding: var(--reader-controls-padding);
+            background: var(--reader-controls-surface);
+            position: relative;
+            border-right: 1px solid var(--reader-controls-border);
+            box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.7);
+            backdrop-filter: blur(14px);
+            z-index: 5;
+            align-self: stretch;
+            justify-content: flex-start;
+            align-items: center;
+        }
+
+        body.reader-position-left #readerControls,
+        body.reader-position-right #readerControls {
+            flex-direction: column;
+            width: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2);
+        }
+
+        body.reader-position-right #readerControls {
+            order: 2;
+            border-right: none;
+            border-left: 1px solid var(--reader-controls-border);
+            box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.7);
+        }
+
+        body.reader-position-top #readerControls,
+        body.reader-position-bottom #readerControls {
+            flex-direction: row;
+            width: auto;
+            min-height: calc(var(--reader-controls-size) + var(--reader-controls-padding) * 2);
+            border-right: none;
+            border-bottom: 1px solid var(--reader-controls-border);
+            box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.7);
+            overflow-x: auto;
+            overflow-y: hidden;
+        }
+
+        body.reader-position-bottom #readerControls {
+            order: 2;
+            border-bottom: none;
+            border-top: 1px solid var(--reader-controls-border);
+            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
+        }
+
+        #readerControls.collapsed {
+            padding: 10px 8px;
+            gap: 6px;
+        }
+
+        body.reader-position-left #readerControls.collapsed,
+        body.reader-position-right #readerControls.collapsed {
+            width: calc(var(--reader-controls-size) + 16px);
+        }
+
+        #readerControls.collapsed .reader-tool:not(.reader-tool-collapse),
+        #readerControls.collapsed .reader-settings-launcher {
+            display: none;
+        }
+
+        .reader-tools {
+            display: flex;
+            gap: var(--reader-controls-gap);
+            flex: 1;
+            min-width: 0;
+        }
+
+        body.reader-position-left .reader-tools,
+        body.reader-position-right .reader-tools {
+            flex-direction: column;
+            align-items: center;
+        }
+
+        body.reader-position-top .reader-tools,
+        body.reader-position-bottom .reader-tools {
+            flex-direction: row;
+            align-items: center;
+        }
+
+        .reader-tool,
+        .reader-settings-launcher {
+            width: var(--reader-controls-size);
+            min-width: var(--reader-controls-size);
+            height: var(--reader-controls-size);
+            border: 1px solid rgba(15, 23, 42, 0.12);
+            border-radius: calc(var(--reader-controls-radius) - 4px);
+            background: rgba(255, 255, 255, 0.88);
+            color: #0f172a;
+            display: inline-flex;
+            align-items: center;
+            justify-content: center;
+            cursor: pointer;
+            transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
+            box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
+            padding: 0;
+        }
+
+        .reader-tool:hover,
+        .reader-settings-launcher:hover {
+            background: #f8fafc;
+            border-color: rgba(15, 23, 42, 0.2);
+        }
+
+        .reader-tool:active,
+        .reader-settings-launcher:active {
+            transform: translateY(1px);
+        }
+
+        .reader-tool.is-active {
+            background: #e2e8f0;
+            border-color: rgba(15, 23, 42, 0.22);
+        }
+
+        .reader-tool.reader-tool-danger {
+            color: #b91c1c;
+        }
+
+        .reader-tool.reader-tool-accent {
+            color: #166534;
+        }
+
+        .reader-tool svg,
+        .reader-settings-launcher svg {
+            width: var(--reader-controls-icon);
+            height: var(--reader-controls-icon);
+            stroke: currentColor;
+            fill: none;
+            stroke-width: 1.8;
+            stroke-linecap: round;
+            stroke-linejoin: round;
+        }
+
+        .reader-tool-label {
+            display: var(--reader-controls-label);
+        }
+
+        .reader-tool-stack {
+            display: inline-flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            gap: 4px;
+            font-size: var(--reader-controls-font);
+            line-height: 1;
+            letter-spacing: 0.02em;
+        }
+
+        #readerControlsSpacer {
+            display: none;
+            flex: 1;
+            min-width: 8px;
+            min-height: 8px;
+        }
+
+        body.reader-position-left #readerControlsSpacer,
+        body.reader-position-right #readerControlsSpacer {
+            display: block;
+        }
+
+        .reader-settings-panel {
+            display: none;
+            position: absolute;
+            inset: auto auto 12px 12px;
+            width: min(320px, calc(100vw - 24px));
+            padding: 14px;
+            background: rgba(255, 255, 255, 0.98);
+            border: 1px solid rgba(15, 23, 42, 0.1);
+            border-radius: 18px;
+            box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);
+            z-index: 20;
+        }
+
+        #readerControls.settings-open .reader-settings-panel {
+            display: block;
+        }
+
+        body.reader-position-right .reader-settings-panel {
+            inset: auto 12px 12px auto;
+        }
+
+        body.reader-position-top .reader-settings-panel {
+            inset: 12px auto auto 12px;
+        }
+
+        body.reader-position-bottom .reader-settings-panel {
+            inset: auto auto 12px 12px;
+        }
+
+        .reader-settings-title {
+            font-size: 13px;
+            font-weight: 700;
+            margin-bottom: 12px;
+            color: #0f172a;
+        }
+
+        .reader-settings-group {
+            margin-bottom: 14px;
+        }
+
+        .reader-settings-group:last-child {
+            margin-bottom: 0;
+        }
+
+        .reader-settings-label {
+            display: block;
+            font-size: 11px;
+            font-weight: 700;
+            color: #475569;
+            margin-bottom: 8px;
+            text-transform: uppercase;
+            letter-spacing: 0.06em;
+        }
+
+        .reader-settings-options {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+        }
+
+        .reader-settings-option {
+            border: 1px solid rgba(15, 23, 42, 0.12);
+            background: #fff;
+            border-radius: 999px;
+            font-size: 12px;
+            color: #0f172a;
+            padding: 6px 10px;
+            cursor: pointer;
+        }
+
+        .reader-settings-option.is-selected {
+            background: #0f172a;
+            color: #fff;
+            border-color: #0f172a;
+        }
 
         /* 顶部状态条 */
         #loading-indicator {
@@ -240,12 +527,40 @@
         body.night-reading-mode #loading-indicator,
         body.night-reading-mode .user-menu-trigger,
         body.night-reading-mode .user-menu-panel,
-        body.night-reading-mode .modal {
+        body.night-reading-mode .modal,
+        body.night-reading-mode #readerControls,
+        body.night-reading-mode .reader-settings-panel {
             box-shadow: 0 5px 15px rgba(0, 0, 0, 0.45);
         }
 
-        body.night-reading-mode .bottom-bar {
-            background-color: rgba(15, 23, 42, 0.18);
+        body.night-reading-mode #readerWorkspace {
+            background: linear-gradient(180deg, rgba(15, 23, 42, 0.98), rgba(15, 23, 42, 0.9));
+        }
+
+        body.night-reading-mode #readerControls,
+        body.night-reading-mode .reader-settings-panel {
+            background: rgba(15, 23, 42, 0.96);
+            border-color: rgba(148, 163, 184, 0.16);
+        }
+
+        body.night-reading-mode .reader-tool,
+        body.night-reading-mode .reader-settings-launcher,
+        body.night-reading-mode .reader-settings-option {
+            background: rgba(30, 41, 59, 0.92);
+            color: #e2e8f0;
+            border-color: rgba(148, 163, 184, 0.16);
+        }
+
+        body.night-reading-mode .reader-tool.is-active,
+        body.night-reading-mode .reader-settings-option.is-selected {
+            background: #e2e8f0;
+            color: #0f172a;
+            border-color: #e2e8f0;
+        }
+
+        body.night-reading-mode .reader-settings-title,
+        body.night-reading-mode .reader-settings-label {
+            color: #e2e8f0;
         }
 
         body.night-reading-mode .pdfViewer .page {
@@ -259,21 +574,26 @@
                 padding: 10px;
             }
 
-            .button {
-                font-size: 10px;
-                padding: 4px 4px;
-                margin: 0 2px;
-            }
-
-            .bottom-bar {
-                padding: 6px 0;
-            }
-
-            #loading-indicator {
-                font-size: 10px;
-                padding: 3px 5px;
-            }
-        }
+            .button {
+                font-size: 10px;
+                padding: 4px 4px;
+                margin: 0 2px;
+            }
+
+            #readerControls {
+                --reader-controls-padding: 8px;
+                --reader-controls-gap: 6px;
+            }
+
+            #loading-indicator {
+                font-size: 10px;
+                padding: 3px 5px;
+            }
+
+            .reader-settings-panel {
+                width: min(280px, calc(100vw - 20px));
+            }
+        }
     </style>
 </head>
 
@@ -287,17 +607,7 @@
         </div>
     </div>
 
-    <audio id="audio-player" controls></audio>
-
-    <div class="bottom-bar">
-        <button id="uploadButton" class="button">上传 PDF</button>
-        <button id="openDirectoryButton" class="button">打开目录</button>
-        <button id="nightModeButton" class="button button-night">夜间模式</button>
-        <button id="select-all-text" class="button button-light">阅读整页</button>
-        <button id="pause-play-button" class="button button-pause">暂停播放</button>
-        <button id="stop-play-button" class="button button-stop">停止</button>
-        <button id="toggle-text-select" class="button button-success">文本选择</button>
-    </div>
+    <audio id="audio-player" controls></audio>
 
     <input type="file" id="pdfInput" accept="application/pdf" style="display: none;" />
 
@@ -374,9 +684,9 @@
             <div id="sidebarResizer"></div>
         </div>
 
-        <div id="mainContainer">
-            <div class="toolbar">
-                <div id="toolbarContainer">
+        <div id="mainContainer">
+            <div class="toolbar">
+                <div id="toolbarContainer">
                     <div id="toolbarViewer" class="toolbarHorizontalGroup">
                         <div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
                             <button id="sidebarToggleButton" class="toolbarButton" type="button"
@@ -815,9 +1125,87 @@
                         </div>
                     </div>
                 </div>
-                <div id="viewerContainer" tabindex="0">
-                    <div id="viewer" class="pdfViewer"></div>
-                </div>
+            <div id="readerWorkspace">
+                <aside id="readerControls" class="reader-controls" aria-label="阅读控制栏">
+                    <button id="readerControlsCollapse" class="reader-tool reader-tool-collapse" type="button" title="折叠或展开控制栏" aria-label="折叠或展开控制栏">
+                        <span class="reader-tool-stack">
+                            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 6l-6 6 6 6"/></svg>
+                            <span class="reader-tool-label">收起</span>
+                        </span>
+                    </button>
+                    <div class="reader-tools">
+                        <button id="uploadButton" class="reader-tool" type="button" title="上传 PDF" aria-label="上传 PDF">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M4 20h16"/></svg>
+                                <span class="reader-tool-label">上传</span>
+                            </span>
+                        </button>
+                        <button id="openDirectoryButton" class="reader-tool" type="button" title="打开目录" aria-label="打开目录">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7h5l2 2h11v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 7V5a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v2"/></svg>
+                                <span class="reader-tool-label">目录</span>
+                            </span>
+                        </button>
+                        <button id="nightModeButton" class="reader-tool" type="button" title="夜间模式" aria-label="夜间模式">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
+                                <span class="reader-tool-label">夜间</span>
+                            </span>
+                        </button>
+                        <button id="select-all-text" class="reader-tool" type="button" title="阅读整页" aria-label="阅读整页">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 4h9l3 3v13H6z"/><path d="M15 4v4h4"/><path d="M9 12h6"/><path d="M9 16h6"/></svg>
+                                <span class="reader-tool-label">整页</span>
+                            </span>
+                        </button>
+                        <button id="pause-play-button" class="reader-tool" type="button" title="暂停播放" aria-label="暂停播放">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5v14"/><path d="M15 5v14"/></svg>
+                                <span class="reader-tool-label">暂停</span>
+                            </span>
+                        </button>
+                        <button id="stop-play-button" class="reader-tool reader-tool-danger" type="button" title="停止播放" aria-label="停止播放">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7h10v10H7z"/></svg>
+                                <span class="reader-tool-label">停止</span>
+                            </span>
+                        </button>
+                        <button id="toggle-text-select" class="reader-tool reader-tool-accent" type="button" title="文本选择" aria-label="文本选择">
+                            <span class="reader-tool-stack">
+                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7V4h3"/><path d="M20 7V4h-3"/><path d="M4 17v3h3"/><path d="M20 17v3h-3"/><path d="M8 12h8"/></svg>
+                                <span class="reader-tool-label">选读</span>
+                            </span>
+                        </button>
+                    </div>
+                    <div id="readerControlsSpacer"></div>
+                    <button id="readerSettingsButton" class="reader-settings-launcher" type="button" title="设置" aria-label="设置" aria-expanded="false">
+                        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v3"/><path d="M12 18v3"/><path d="M3 12h3"/><path d="M18 12h3"/><path d="M5.6 5.6l2.1 2.1"/><path d="M16.3 16.3l2.1 2.1"/><path d="M18.4 5.6l-2.1 2.1"/><path d="M7.7 16.3l-2.1 2.1"/><circle cx="12" cy="12" r="3.5"/></svg>
+                    </button>
+                    <div id="readerSettingsPanel" class="reader-settings-panel">
+                        <div class="reader-settings-title">阅读栏设置</div>
+                        <div class="reader-settings-group">
+                            <label class="reader-settings-label">位置</label>
+                            <div class="reader-settings-options" data-setting-group="position">
+                                <button class="reader-settings-option" type="button" data-position="left">左侧</button>
+                                <button class="reader-settings-option" type="button" data-position="right">右侧</button>
+                                <button class="reader-settings-option" type="button" data-position="top">顶部</button>
+                                <button class="reader-settings-option" type="button" data-position="bottom">底部</button>
+                            </div>
+                        </div>
+                        <div class="reader-settings-group">
+                            <label class="reader-settings-label">尺寸</label>
+                            <div class="reader-settings-options" data-setting-group="size">
+                                <button class="reader-settings-option" type="button" data-size="small">小</button>
+                                <button class="reader-settings-option" type="button" data-size="medium">中</button>
+                                <button class="reader-settings-option" type="button" data-size="large">大</button>
+                            </div>
+                        </div>
+                    </div>
+                </aside>
+                <div id="viewerContainer" tabindex="0">
+                    <div id="viewer" class="pdfViewer"></div>
+                </div>
+            </div>
             </div>
 
             <div id="dialogContainer">
@@ -1154,26 +1542,82 @@
                 const pausePlayButton = document.getElementById('pause-play-button');
                 const stopPlayButton = document.getElementById('stop-play-button');
                 const nightModeButton = document.getElementById('nightModeButton');
-                const bottomBar = document.querySelector('.bottom-bar');
                 const userMenu = document.getElementById('userMenu');
                 const userMenuTrigger = document.getElementById('userMenuTrigger');
                 const userMenuPanel = document.getElementById('userMenuPanel');
                 const userLogoutBtn = document.getElementById('userLogoutBtn');
-                const userAdminBtn = document.getElementById('userAdminBtn');
-
-                let selectedFile = null;
-                let currentPage = 1;
-                const itemsPerPage = 10;
+                const userAdminBtn = document.getElementById('userAdminBtn');
+                const readerControls = document.getElementById('readerControls');
+                const readerControlsCollapse = document.getElementById('readerControlsCollapse');
+                const readerSettingsButton = document.getElementById('readerSettingsButton');
+                const readerSettingsPanel = document.getElementById('readerSettingsPanel');
+
+                let selectedFile = null;
+                let currentPage = 1;
+                const itemsPerPage = 10;
                 let allFiles = [];
                 let selectedStartContext = null;
                 const currentFilePath = new URLSearchParams(window.location.search).get('file') || '';
                 let isRestoringProgress = false;
                 const NIGHT_MODE_STORAGE_KEY = 'reader_pro.night_mode';
+                const READER_POSITION_STORAGE_KEY = 'reader_pro.controls.position';
+                const READER_SIZE_STORAGE_KEY = 'reader_pro.controls.size';
+                const READER_COLLAPSED_STORAGE_KEY = 'reader_pro.controls.collapsed';
+
+                function updateReaderToggleIcon() {
+                    const icon = readerControlsCollapse.querySelector('svg path');
+                    const position = document.body.dataset.readerPosition || 'left';
+                    const collapsed = readerControls.classList.contains('collapsed');
+                    const directionMap = {
+                        left: collapsed ? 'M9 6l6 6-6 6' : 'M15 6l-6 6 6 6',
+                        right: collapsed ? 'M15 6l-6 6 6 6' : 'M9 6l6 6-6 6',
+                        top: collapsed ? 'M6 15l6-6 6 6' : 'M6 9l6 6 6-6',
+                        bottom: collapsed ? 'M6 9l6 6 6-6' : 'M6 15l6-6 6 6'
+                    };
+                    icon.setAttribute('d', directionMap[position] || directionMap.left);
+                }
+
+                function setReaderControlsCollapsed(collapsed) {
+                    readerControls.classList.toggle('collapsed', collapsed);
+                    readerControlsCollapse.setAttribute('aria-expanded', String(!collapsed));
+                    localStorage.setItem(READER_COLLAPSED_STORAGE_KEY, collapsed ? '1' : '0');
+                    updateReaderToggleIcon();
+                }
+
+                function setReaderControlsPosition(position) {
+                    const normalized = ['left', 'right', 'top', 'bottom'].includes(position) ? position : 'left';
+                    document.body.classList.remove('reader-position-left', 'reader-position-right', 'reader-position-top', 'reader-position-bottom');
+                    document.body.classList.add(`reader-position-${normalized}`);
+                    document.body.dataset.readerPosition = normalized;
+                    localStorage.setItem(READER_POSITION_STORAGE_KEY, normalized);
+                    document.querySelectorAll('[data-position]').forEach(button => {
+                        button.classList.toggle('is-selected', button.dataset.position === normalized);
+                    });
+                    updateReaderToggleIcon();
+                }
+
+                function setReaderControlsSize(size) {
+                    const normalized = ['small', 'medium', 'large'].includes(size) ? size : 'small';
+                    document.body.classList.remove('reader-size-small', 'reader-size-medium', 'reader-size-large');
+                    document.body.classList.add(`reader-size-${normalized}`);
+                    document.body.dataset.readerSize = normalized;
+                    localStorage.setItem(READER_SIZE_STORAGE_KEY, normalized);
+                    document.querySelectorAll('[data-size]').forEach(button => {
+                        button.classList.toggle('is-selected', button.dataset.size === normalized);
+                    });
+                }
+
+                function initReaderControls() {
+                    const defaultPosition = window.innerWidth <= 768 ? 'bottom' : 'left';
+                    setReaderControlsPosition(localStorage.getItem(READER_POSITION_STORAGE_KEY) || defaultPosition);
+                    setReaderControlsSize(localStorage.getItem(READER_SIZE_STORAGE_KEY) || 'small');
+                    setReaderControlsCollapsed(localStorage.getItem(READER_COLLAPSED_STORAGE_KEY) === '1');
+                }
 
                 function updateNightModeButton(isEnabled) {
-                    nightModeButton.textContent = isEnabled ? '日间模式' : '夜间模式';
-                    nightModeButton.classList.toggle('button-light', isEnabled);
-                    nightModeButton.classList.toggle('button-night', !isEnabled);
+                    nightModeButton.classList.toggle('is-active', isEnabled);
+                    nightModeButton.title = isEnabled ? '日间模式' : '夜间模式';
+                    nightModeButton.setAttribute('aria-label', isEnabled ? '日间模式' : '夜间模式');
                 }
 
                 function setNightMode(enabled) {
@@ -1245,64 +1689,38 @@
                     window.location.href = '/admin';
                 });
                 initNightMode();
+                initReaderControls();
                 nightModeButton.addEventListener('click', function () {
                     const nextEnabled = !document.body.classList.contains('night-reading-mode');
                     setNightMode(nextEnabled);
                     window.location.reload();
                 });
-
-                function syncBottomBarToPage() {
-                    if (!bottomBar) return;
-
-                    const app = window.PDFViewerApplication;
-                    const currentPageNumber = app && app.pdfViewer ? app.pdfViewer.currentPageNumber : null;
-                    const pageEl = currentPageNumber
-                        ? document.querySelector(`.page[data-page-number="${currentPageNumber}"]`)
-                        : document.querySelector('.page');
-
-                    if (!pageEl) {
-                        bottomBar.style.width = '75%';
-                        bottomBar.style.left = '50%';
-                        return;
-                    }
-
-                    const rect = pageEl.getBoundingClientRect();
-                    if (!rect.width) return;
-
-                    const viewportWidth = window.innerWidth;
-                    const maxWidth = Math.max(240, viewportWidth - 16);
-                    const width = Math.min(rect.width, maxWidth);
-                    bottomBar.style.width = `${Math.round(width)}px`;
-
-                    let centerX = rect.left + rect.width / 2;
-                    const minCenter = width / 2 + 8;
-                    const maxCenter = viewportWidth - width / 2 - 8;
-                    centerX = Math.min(maxCenter, Math.max(minCenter, centerX));
-                    bottomBar.style.left = `${Math.round(centerX)}px`;
-                }
-
-                function setupBottomBarSync() {
-                    const viewerContainer = document.getElementById('viewerContainer');
-                    if (viewerContainer) {
-                        viewerContainer.addEventListener('scroll', () => requestAnimationFrame(syncBottomBarToPage), { passive: true });
-                    }
-                    window.addEventListener('resize', () => requestAnimationFrame(syncBottomBarToPage), { passive: true });
-
-                    const tryBindPdfEvents = () => {
-                        const app = window.PDFViewerApplication;
-                        const eventBus = app ? app.eventBus : null;
-                        if (!eventBus) {
-                            setTimeout(tryBindPdfEvents, 200);
-                            return;
-                        }
-                        ['pagechanging', 'scalechanging', 'updateviewarea', 'pagerendered'].forEach(evt => {
-                            eventBus.on(evt, () => requestAnimationFrame(syncBottomBarToPage));
-                        });
-                        requestAnimationFrame(syncBottomBarToPage);
-                    };
-                    tryBindPdfEvents();
-                }
-                setupBottomBarSync();
+                readerControlsCollapse.addEventListener('click', () => {
+                    setReaderControlsCollapsed(!readerControls.classList.contains('collapsed'));
+                });
+                readerSettingsButton.addEventListener('click', event => {
+                    event.stopPropagation();
+                    const nextOpen = !readerControls.classList.contains('settings-open');
+                    readerControls.classList.toggle('settings-open', nextOpen);
+                    readerSettingsButton.setAttribute('aria-expanded', String(nextOpen));
+                });
+                document.addEventListener('click', event => {
+                    if (!readerControls.contains(event.target)) {
+                        readerControls.classList.remove('settings-open');
+                        readerSettingsButton.setAttribute('aria-expanded', 'false');
+                    }
+                });
+                readerSettingsPanel.querySelectorAll('[data-position]').forEach(button => {
+                    button.addEventListener('click', () => setReaderControlsPosition(button.dataset.position));
+                });
+                readerSettingsPanel.querySelectorAll('[data-size]').forEach(button => {
+                    button.addEventListener('click', () => setReaderControlsSize(button.dataset.size));
+                });
+                window.addEventListener('resize', () => {
+                    if (!localStorage.getItem(READER_POSITION_STORAGE_KEY)) {
+                        setReaderControlsPosition(window.innerWidth <= 768 ? 'bottom' : 'left');
+                    }
+                }, { passive: true });
 
                 async function saveReadingProgress(pageNumber) {
                     if (!currentFilePath || !Number.isInteger(pageNumber) || pageNumber < 1) return;
@@ -1577,22 +1995,25 @@
                 }
 
                 // 切换文本选择监听
-                document.getElementById('toggle-text-select').addEventListener('click', () => {
-                    isListening = !isListening;
-                    const button = document.getElementById('toggle-text-select');
-                    button.textContent = isListening ? "关闭监听" : "文本选择";
-                    button.classList.toggle('button-danger', isListening);
-                });
+                document.getElementById('toggle-text-select').addEventListener('click', () => {
+                    isListening = !isListening;
+                    const button = document.getElementById('toggle-text-select');
+                    button.classList.toggle('is-active', isListening);
+                    button.title = isListening ? '关闭监听' : '文本选择';
+                    button.setAttribute('aria-label', isListening ? '关闭监听' : '文本选择');
+                });
 
                 // 暂停/继续播放
-                pausePlayButton.addEventListener('click', () => {
-                    isPaused = !isPaused;
-                    pausePlayButton.textContent = isPaused ? "继续播放" : "暂停播放";
-                    if (isPaused && currentAudio) {
-                        currentAudio.pause();
-                        if (audioContext) audioContext.suspend();
-                    } else if (!isPaused && currentAudio) {
-                        currentAudio.play();
+                pausePlayButton.addEventListener('click', () => {
+                    isPaused = !isPaused;
+                    pausePlayButton.classList.toggle('is-active', isPaused);
+                    pausePlayButton.title = isPaused ? '继续播放' : '暂停播放';
+                    pausePlayButton.setAttribute('aria-label', isPaused ? '继续播放' : '暂停播放');
+                    if (isPaused && currentAudio) {
+                        currentAudio.pause();
+                        if (audioContext) audioContext.suspend();
+                    } else if (!isPaused && currentAudio) {
+                        currentAudio.play();
                         if (audioContext) audioContext.resume();
                     }
                 });
@@ -1615,12 +2036,14 @@
                         currentAudio.pause();
                         currentAudio.src = '';
                         currentAudio = null;
-                    }
-                    isPaused = false;
-                    pausePlayButton.textContent = "暂停播放";
-                    if (showTip) {
-                        const loadingIndicator = document.getElementById('loading-indicator');
-                        loadingIndicator.textContent = '已停止';
+                    }
+                    isPaused = false;
+                    pausePlayButton.classList.remove('is-active');
+                    pausePlayButton.title = '暂停播放';
+                    pausePlayButton.setAttribute('aria-label', '暂停播放');
+                    if (showTip) {
+                        const loadingIndicator = document.getElementById('loading-indicator');
+                        loadingIndicator.textContent = '已停止';
                         loadingIndicator.style.display = 'block';
                         setTimeout(() => {
                             loadingIndicator.style.display = 'none';
@@ -1699,10 +2122,11 @@
                     return { word, occurrence };
                 }
 
-                function updateReadPageButtonText() {
-                    selectedStartContext = getSelectedContextOnPage();
-                    readPageButton.textContent = selectedStartContext ? '从此阅读' : '阅读整页';
-                }
+                function updateReadPageButtonText() {
+                    selectedStartContext = getSelectedContextOnPage();
+                    readPageButton.title = selectedStartContext ? '从此阅读' : '阅读整页';
+                    readPageButton.setAttribute('aria-label', selectedStartContext ? '从此阅读' : '阅读整页');
+                }
                 document.addEventListener('selectionchange', updateReadPageButtonText);
                 updateReadPageButtonText();