Spaces:
Running
Running
Ctrl+K
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TieReader - Complete Reader with Toggleable Audio Controls</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css"> <style> :root { --primary-color: #667eea; --secondary-color: #764ba2; --accent-color: #f093fb; --text-primary: #2d3748; --text-secondary: #4a5568; --bg-primary: #ffffff; --bg-secondary: #f7fafc; --border-color: #e2e8f0; } .gradient-bg { background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); } .gradient-text { background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .book-card { transition: all 0.3s ease; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); } .book-card:hover { transform: translateY(-8px); box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); } .import-zone { border: 3px dashed var(--primary-color); transition: all 0.3s ease; } .import-zone.drag-over { border-color: var(--secondary-color); background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%); transform: scale(1.02); } .audio-bar { transform: translateY(100%); transition: transform 0.3s ease; background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); backdrop-filter: blur(10px); } .audio-bar.show { transform: translateY(0); } .audio-toggle-btn { position: fixed; right: 20px; bottom: 20px; z-index: 1001; background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); transition: all 0.3s ease; } .audio-toggle-btn:hover { transform: scale(1.1); box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); } .reading-content { font-family: 'Georgia', serif; line-height: 1.8; max-width: 800px; margin: 0 auto; padding: 2rem; } .toc-item { transition: all 0.2s ease; border-left: 3px solid transparent; } .toc-item:hover { border-left-color: var(--primary-color); background: rgba(102, 126, 234, 0.1); } .progress-bar { height: 4px; background: linear-gradient(90deg, var(--primary-color) 0%, var(--secondary-color) 100%); border-radius: 2px; transition: width 0.3s ease; } .collection-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1.5rem; } .highlight { background: linear-gradient(120deg, #a8edea 0%, #fed6e3 100%); padding: 2px 4px; border-radius: 3px; animation: highlight-pulse 2s ease-in-out; } @keyframes highlight-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } .theme-dark { --bg-primary: #1a202c; --bg-secondary: #2d3748; --text-primary: #e2e8f0; --text-secondary: #a0aec0; --border-color: #4a5568; } .theme-sepia { --bg-primary: #f4f1ea; --bg-secondary: #ede7d8; --text-primary: #5d4e37; --text-secondary: #8b7355; } .modal-backdrop { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(5px); } .slide-in { animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .fade-in { animation: fadeIn 0.5s ease-in; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .pulse-animation { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } </style> </head> <body class="bg-gray-50 min-h-screen"> <!-- Header --> <header class="gradient-bg text-white shadow-lg"> <div class="container mx-auto px-4 py-6"> <div class="flex items-center justify-between"> <div class="flex items-center space-x-4"> <i class="fas fa-book-open text-3xl"></i> <h1 class="text-3xl font-bold">TieReader</h1> <span class="text-sm opacity-75">Complete Multi-Format Reader</span> </div> <div class="flex items-center space-x-4"> <button id="themeToggle" class="p-2 rounded-lg bg-white bg-opacity-20 hover:bg-opacity-30 transition-all"> <i class="fas fa-palette"></i> </button> <button id="settingsBtn" class="p-2 rounded-lg bg-white bg-opacity-20 hover:bg-opacity-30 transition-all"> <i class="fas fa-cog"></i> </button> </div> </div> </div> </header> <!-- Navigation --> <nav class="bg-white shadow-sm border-b"> <div class="container mx-auto px-4"> <div class="flex space-x-8"> <button class="nav-btn flex items-center space-x-2 px-4 py-4 border-b-2 border-transparent hover:border-blue-500 transition-all" data-tab="library"> <i class="fas fa-book"></i> <span>Library</span> </button> <button class="nav-btn flex items-center space-x-2 px-4 py-4 border-b-2 border-transparent hover:border-blue-500 transition-all" data-tab="my-store"> <i class="fas fa-store"></i> <span>My Store</span> </button> <button class="nav-btn flex items-center space-x-2 px-4 py-4 border-b-2 border-transparent hover:border-blue-500 transition-all" data-tab="reading"> <i class="fas fa-book-reader"></i> <span>Reading</span> </button> <button class="nav-btn flex items-center space-x-2 px-4 py-4 border-b-2 border-transparent hover:border-blue-500 transition-all" data-tab="import"> <i class="fas fa-upload"></i> <span>Import EPUB</span> </button> </div> </div> </nav> <!-- Main Content --> <main class="container mx-auto px-4 py-8"> <!-- Library Tab --> <div id="library-tab" class="tab-content"> <div class="flex justify-between items-center mb-6"> <h2 class="text-2xl font-bold gradient-text">Book Library</h2> <div class="flex space-x-4"> <input type="text" id="librarySearch" placeholder="Search books..." class="px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"> <select id="librarySort" class="px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"> <option value="title">Sort by Title</option> <option value="author">Sort by Author</option> <option value="date">Sort by Date</option> <option value="rating">Sort by Rating</option> </select> </div> </div> <div id="libraryGrid" class="collection-grid"> <!-- Sample books will be populated here --> </div> </div> <!-- My Store Tab --> <div id="my-store-tab" class="tab-content hidden"> <div class="flex justify-between items-center mb-6"> <h2 class="text-2xl font-bold gradient-text">My Personal Store</h2> <div class="flex space-x-4"> <button id="createCollectionBtn" class="px-4 py-2 gradient-bg text-white rounded-lg hover:opacity-90 transition-all"> <i class="fas fa-plus mr-2"></i>Create Collection </button> <button id="importToStoreBtn" class="px-4 py-2 border border-blue-500 text-blue-500 rounded-lg hover:bg-blue-50 transition-all"> <i class="fas fa-download mr-2"></i>Import Books </button> </div> </div> <!-- Collections --> <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8"> <div class="collection-card bg-white p-4 rounded-lg shadow-sm border-2 border-blue-200 cursor-pointer hover:shadow-md transition-all" data-collection="favorites"> <div class="flex items-center space-x-3"> <i class="fas fa-star text-yellow-500 text-xl"></i> <div> <h3 class="font-semibold">Favorites</h3> <p class="text-sm text-gray-600" id="favoritesCount">0 books</p> </div> </div> </div> <div class="collection-card bg-white p-4 rounded-lg shadow-sm border-2 border-green-200 cursor-pointer hover:shadow-md transition-all" data-collection="reading"> <div class="flex items-center space-x-3"> <i class="fas fa-book-open text-green-500 text-xl"></i> <div> <h3 class="font-semibold">Currently Reading</h3> <p class="text-sm text-gray-600" id="readingCount">0 books</p> </div> </div> </div> <div class="collection-card bg-white p-4 rounded-lg shadow-sm border-2 border-purple-200 cursor-pointer hover:shadow-md transition-all" data-collection="completed"> <div class="flex items-center space-x-3"> <i class="fas fa-check-circle text-purple-500 text-xl"></i> <div> <h3 class="font-semibold">Completed</h3> <p class="text-sm text-gray-600" id="completedCount">0 books</p> </div> </div> </div> <div class="collection-card bg-white p-4 rounded-lg shadow-sm border-2 border-orange-200 cursor-pointer hover:shadow-md transition-all" data-collection="to-read"> <div class="flex items-center space-x-3"> <i class="fas fa-clock text-orange-500 text-xl"></i> <div> <h3 class="font-semibold">To Read</h3> <p class="text-sm text-gray-600" id="toReadCount">0 books</p> </div> </div> </div> </div> <div id="myStoreGrid" class="collection-grid"> <!-- My Store books will be populated here --> </div> </div> <!-- Reading Tab --> <div id="reading-tab" class="tab-content hidden"> <div class="flex"> <!-- Table of Contents Sidebar --> <div id="tocSidebar" class="w-80 bg-white rounded-lg shadow-sm p-6 mr-6 hidden"> <div class="flex justify-between items-center mb-4"> <h3 class="text-lg font-semibold">Table of Contents</h3> <button id="closeToc" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-times"></i> </button> </div> <div id="tocContent" class="space-y-2 max-h-96 overflow-y-auto"> <!-- TOC items will be populated here --> </div> </div> <!-- Reading Content --> <div class="flex-1"> <div class="bg-white rounded-lg shadow-sm p-6"> <!-- Reading Controls --> <div class="flex justify-between items-center mb-6 border-b pb-4"> <div class="flex items-center space-x-4"> <button id="showTocBtn" class="px-4 py-2 border rounded-lg hover:bg-gray-50 transition-all"> <i class="fas fa-list mr-2"></i>Contents </button> <button id="bookmarkBtn" class="px-4 py-2 border rounded-lg hover:bg-gray-50 transition-all"> <i class="fas fa-bookmark mr-2"></i>Bookmark </button> <button id="annotateBtn" class="px-4 py-2 border rounded-lg hover:bg-gray-50 transition-all"> <i class="fas fa-pencil-alt mr-2"></i>Annotate </button> </div> <div class="flex items-center space-x-4"> <button id="decreaseFontBtn" class="p-2 border rounded hover:bg-gray-50"> <i class="fas fa-minus"></i> </button> <span id="fontSizeDisplay" class="text-sm">16px</span> <button id="increaseFontBtn" class="p-2 border rounded hover:bg-gray-50"> <i class="fas fa-plus"></i> </button> <button id="readingSettingsBtn" class="p-2 border rounded hover:bg-gray-50"> <i class="fas fa-cog"></i> </button> </div> </div> <!-- Progress Bar --> <div class="mb-6"> <div class="flex justify-between text-sm text-gray-600 mb-2"> <span id="chapterInfo">Chapter 1 of 15</span> <span id="readingProgress">15% complete</span> </div> <div class="w-full bg-gray-200 rounded-full h-2"> <div id="progressBar" class="progress-bar h-2 rounded-full" style="width: 15%"></div> </div> </div> <!-- Book Content --> <div id="bookContent" class="reading-content"> <h1 class="text-3xl font-bold mb-6">Welcome to TieReader</h1> <p class="mb-4">TieReader is a comprehensive multi-format book reading application that supports both custom .tie files and standard EPUB format. Experience the future of digital reading with advanced features designed for book lovers.</p> <h2 class="text-2xl font-semibold mb-4">Key Features</h2> <ul class="list-disc list-inside mb-6 space-y-2"> <li><strong>Multi-Format Support:</strong> Read .tie and .epub files seamlessly</li> <li><strong>Personal Library:</strong> Organize your books in custom collections</li> <li><strong>Audio Narration:</strong> Text-to-speech with customizable voices</li> <li><strong>Advanced Reading Tools:</strong> Bookmarks, annotations, and highlighting</li> <li><strong>Responsive Design:</strong> Perfect reading experience on any device</li> <li><strong>Import Functionality:</strong> Easy import from external sources</li> </ul> <h2 class="text-2xl font-semibold mb-4">Getting Started</h2> <p class="mb-4">To begin your reading journey:</p> <ol class="list-decimal list-inside mb-6 space-y-2"> <li>Browse the Library to discover available books</li> <li>Use the Import feature to add your own EPUB files</li> <li>Organize your collection in My Store</li> <li>Customize your reading experience with themes and fonts</li> <li>Use the audio toggle button to enable text-to-speech</li> </ol> <p class="mb-4">The floating audio button on the right side of your screen provides quick access to audio controls. Click it to reveal the audio bar with play, pause, speed controls, and voice selection options.</p> <h2 class="text-2xl font-semibold mb-4">Advanced Features</h2> <p class="mb-4">TieReader includes sophisticated features for serious readers:</p> <ul class="list-disc list-inside mb-6 space-y-2"> <li>Smart highlighting that follows audio narration</li> <li>Comprehensive table of contents navigation</li> <li>Reading progress tracking across all devices</li> <li>Personal notes and annotation system</li> <li>Multiple reading themes (light, dark, sepia)</li> <li>Adjustable typography and layout options</li> </ul> </div> <!-- Navigation --> <div class="flex justify-between items-center mt-8 pt-6 border-t"> <button id="prevChapterBtn" class="flex items-center space-x-2 px-4 py-2 border rounded-lg hover:bg-gray-50 transition-all"> <i class="fas fa-chevron-left"></i> <span>Previous</span> </button> <span class="text-sm text-gray-600">Page 1 of 1</span> <button id="nextChapterBtn" class="flex items-center space-x-2 px-4 py-2 border rounded-lg hover:bg-gray-50 transition-all"> <span>Next</span> <i class="fas fa-chevron-right"></i> </button> </div> </div> </div> </div> </div> <!-- Import Tab --> <div id="import-tab" class="tab-content hidden"> <div class="text-center mb-8"> <h2 class="text-2xl font-bold gradient-text mb-4">Import EPUB Books</h2> <p class="text-gray-600">Add your own EPUB files to your personal library</p> </div> <!-- Import Methods --> <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8"> <!-- File Upload --> <div class="bg-white rounded-lg shadow-sm p-6"> <h3 class="text-lg font-semibold mb-4"> <i class="fas fa-upload text-blue-500 mr-2"></i>File Upload </h3> <input type="file" id="fileInput" accept=".epub" multiple class="hidden"> <button id="selectFilesBtn" class="w-full px-4 py-3 border-2 border-dashed border-blue-300 rounded-lg hover:border-blue-500 transition-all text-blue-600 hover:bg-blue-50"> <i class="fas fa-file-upload text-2xl mb-2 block"></i> Select EPUB Files </button> </div> <!-- Drag & Drop --> <div class="bg-white rounded-lg shadow-sm p-6"> <h3 class="text-lg font-semibold mb-4"> <i class="fas fa-hand-paper text-green-500 mr-2"></i>Drag & Drop </h3> <div id="dropZone" class="import-zone w-full px-4 py-8 rounded-lg text-center text-green-600"> <i class="fas fa-cloud-upload-alt text-4xl mb-4 block"></i> <p class="text-lg font-medium">Drop EPUB files here</p> <p class="text-sm opacity-75">or click to select files</p> </div> </div> </div> <!-- Import Progress --> <div id="importProgress" class="bg-white rounded-lg shadow-sm p-6 hidden"> <h3 class="text-lg font-semibold mb-4"> <i class="fas fa-cog fa-spin text-blue-500 mr-2"></i>Importing Books </h3> <div id="importList" class="space-y-3"> <!-- Import items will be populated here --> </div> </div> <!-- Import History --> <div class="bg-white rounded-lg shadow-sm p-6"> <h3 class="text-lg font-semibold mb-4"> <i class="fas fa-history text-purple-500 mr-2"></i>Recent Imports </h3> <div id="importHistory" class="space-y-3"> <div class="text-gray-500 text-center py-8"> <i class="fas fa-inbox text-4xl mb-4 block opacity-50"></i> <p>No imports yet. Start by adding some EPUB files!</p> </div> </div> </div> </div> </main> <!-- Audio Toggle Button --> <button id="audioToggleBtn" class="audio-toggle-btn w-16 h-16 rounded-full text-white shadow-lg hover:shadow-xl transition-all"> <i class="fas fa-volume-up text-xl"></i> </button> <!-- Audio Controls Bar --> <div id="audioBar" class="audio-bar fixed bottom-0 left-0 right-0 z-1000 p-4 shadow-lg"> <div class="container mx-auto max-w-4xl"> <div class="flex items-center justify-between"> <!-- Primary Controls --> <div class="flex items-center space-x-4"> <button id="audioPlayBtn" class="w-12 h-12 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-play text-white"></i> </button> <button id="audioPauseBtn" class="w-12 h-12 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all hidden"> <i class="fas fa-pause text-white"></i> </button> <button id="audioStopBtn" class="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-stop text-white text-sm"></i> </button> <button id="audioPrevBtn" class="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-step-backward text-white text-sm"></i> </button> <button id="audioNextBtn" class="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-step-forward text-white text-sm"></i> </button> </div> <!-- Speed & Voice Controls --> <div class="flex items-center space-x-4"> <div class="text-white text-sm"> <span>Speed:</span> <select id="audioSpeed" class="ml-2 px-2 py-1 rounded bg-white bg-opacity-20 text-white border-none"> <option value="0.5">0.5x</option> <option value="0.75">0.75x</option> <option value="1" selected>1x</option> <option value="1.25">1.25x</option> <option value="1.5">1.5x</option> <option value="2">2x</option> </select> </div> <div class="text-white text-sm"> <span>Voice:</span> <select id="audioVoice" class="ml-2 px-2 py-1 rounded bg-white bg-opacity-20 text-white border-none"> <option value="0">Default</option> </select> </div> <button id="audioSettingsBtn" class="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-cog text-white text-sm"></i> </button> </div> <!-- Close Button --> <button id="closeAudioBar" class="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center hover:bg-opacity-30 transition-all"> <i class="fas fa-times text-white text-sm"></i> </button> </div> <!-- Audio Progress --> <div class="mt-4"> <div class="flex justify-between text-white text-sm mb-2"> <span id="audioCurrentTime">00:00</span> <span id="audioTotalTime">00:00</span> </div> <div class="w-full bg-white bg-opacity-20 rounded-full h-2"> <div id="audioProgressBar" class="bg-white h-2 rounded-full transition-all" style="width: 0%"></div> </div> </div> </div> </div> <!-- Modals --> <!-- Settings Modal --> <div id="settingsModal" class="fixed inset-0 modal-backdrop z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4"> <div class="flex justify-between items-center mb-4"> <h3 class="text-lg font-semibold">Reading Settings</h3> <button id="closeSettingsModal" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-times"></i> </button> </div> <div class="space-y-4"> <div> <label class="block text-sm font-medium mb-2">Theme</label> <select id="themeSelect" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"> <option value="light">Light</option> <option value="dark">Dark</option> <option value="sepia">Sepia</option> </select> </div> <div> <label class="block text-sm font-medium mb-2">Font Family</label> <select id="fontFamily" class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"> <option value="Georgia">Georgia</option> <option value="Arial">Arial</option> <option value="Times New Roman">Times New Roman</option> <option value="Helvetica">Helvetica</option> </select> </div> <div> <label class="block text-sm font-medium mb-2">Line Height</label> <input type="range" id="lineHeight" min="1.2" max="2.5" step="0.1" value="1.8" class="w-full"> </div> </div> </div> </div> <script> // TieReader Application class TieReader { constructor() { this.currentBook = null; this.currentChapter = 0; this.myStore = new Map(); this.audioEnabled = false; this.audioBarVisible = false; this.speechSynthesis = window.speechSynthesis; this.currentUtterance = null; this.isPlaying = false; this.fontSize = 16; this.theme = 'light'; this.init(); } init() { this.setupEventListeners(); this.loadSampleBooks(); this.loadVoices(); this.setupDragDrop(); this.initializeAudio(); // Show library tab by default this.showTab('library'); } setupEventListeners() { // Navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tab = e.currentTarget.dataset.tab; this.showTab(tab); }); }); // Audio toggle document.getElementById('audioToggleBtn').addEventListener('click', () => { this.toggleAudioBar(); }); document.getElementById('closeAudioBar').addEventListener('click', () => { this.hideAudioBar(); }); // Audio controls document.getElementById('audioPlayBtn').addEventListener('click', () => { this.playAudio(); }); document.getElementById('audioPauseBtn').addEventListener('click', () => { this.pauseAudio(); }); document.getElementById('audioStopBtn').addEventListener('click', () => { this.stopAudio(); }); // Reading controls document.getElementById('increaseFontBtn').addEventListener('click', () => { this.changeFontSize(2); }); document.getElementById('decreaseFontBtn').addEventListener('click', () => { this.changeFontSize(-2); }); // TOC document.getElementById('showTocBtn').addEventListener('click', () => { this.toggleTOC(); }); document.getElementById('closeToc').addEventListener('click', () => { this.hideTOC(); }); // Settings document.getElementById('settingsBtn').addEventListener('click', () => { this.showSettingsModal(); }); document.getElementById('closeSettingsModal').addEventListener('click', () => { this.hideSettingsModal(); }); // Theme toggle document.getElementById('themeToggle').addEventListener('click', () => { this.cycleTheme(); }); // File import document.getElementById('selectFilesBtn').addEventListener('click', () => { document.getElementById('fileInput').click(); }); document.getElementById('fileInput').addEventListener('change', (e) => { this.handleFileSelect(e.target.files); }); } showTab(tabName) { // Hide all tabs document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.add('hidden'); }); // Show selected tab document.getElementById(`${tabName}-tab`).classList.remove('hidden'); // Update navigation document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.remove('border-blue-500', 'text-blue-600'); if (btn.dataset.tab === tabName) { btn.classList.add('border-blue-500', 'text-blue-600'); } }); } loadSampleBooks() { const sampleBooks = [ { id: 1, title: "The Art of Programming", author: "John Developer", format: "tie", cover: "https://via.placeholder.com/200x300/667eea/ffffff?text=Art+of+Programming", description: "A comprehensive guide to modern programming techniques.", pages: 340, rating: 4.5 }, { id: 2, title: "Digital Design Principles", author: "Sarah Designer", format: "epub", cover: "https://via.placeholder.com/200x300/764ba2/ffffff?text=Design+Principles", description: "Essential principles for creating beautiful digital experiences.", pages: 280, rating: 4.8 }, { id: 3, title: "Machine Learning Fundamentals", author: "Dr. AI Smith", format: "tie", cover: "https://via.placeholder.com/200x300/f093fb/ffffff?text=ML+Fundamentals", description: "Learn the basics of machine learning and AI.", pages: 450, rating: 4.7 }, { id: 4, title: "Web Development Guide", author: "Tech Writer", format: "epub", cover: "https://via.placeholder.com/200x300/4facfe/ffffff?text=Web+Dev+Guide", description: "Complete guide to modern web development.", pages: 520, rating: 4.6 } ]; this.renderBooks(sampleBooks, 'libraryGrid'); } renderBooks(books, containerId) { const container = document.getElementById(containerId); container.innerHTML = ''; books.forEach(book => { const bookCard = document.createElement('div'); bookCard.className = 'book-card rounded-lg shadow-sm p-6 cursor-pointer'; bookCard.innerHTML = ` <div class="mb-4"> <img src="${book.cover}" alt="${book.title}" class="w-full h-48 object-cover rounded-lg mb-4"> <div class="flex items-center justify-between mb-2"> <span class="text-xs px-2 py-1 bg-blue-100 text-blue-800 rounded-full">${book.format.toUpperCase()}</span> <div class="flex items-center text-yellow-500"> <i class="fas fa-star text-sm"></i> <span class="text-sm ml-1">${book.rating}</span> </div> </div> <h3 class="font-semibold text-lg mb-1">${book.title}</h3> <p class="text-gray-600 text-sm mb-2">${book.author}</p> <p class="text-gray-500 text-xs mb-3">${book.description}</p> <p class="text-gray-400 text-xs">${book.pages} pages</p> </div> <div class="flex space-x-2"> <button class="flex-1 px-3 py-2 gradient-bg text-white text-sm rounded-lg hover:opacity-90 transition-all" onclick="tieReader.openBook(${book.id})"> <i class="fas fa-book-open mr-1"></i>Read </button> <button class="px-3 py-2 border border-blue-500 text-blue-500 text-sm rounded-lg hover:bg-blue-50 transition-all" onclick="tieReader.addToStore(${book.id})"> <i class="fas fa-store mr-1"></i>Add to Store </button> </div> `; container.appendChild(bookCard); }); } openBook(bookId) { // Switch to reading tab and load book this.showTab('reading'); this.loadTOC(); // Simulate book loading setTimeout(() => { this.showNotification('Book loaded successfully!', 'success'); }, 500); } addToStore(bookId) { // Add book to My Store this.myStore.set(bookId, { collection: 'to-read', dateAdded: new Date() }); this.updateStoreCounters(); this.showNotification('Book added to My Store!', 'success'); } updateStoreCounters() { const counts = { favorites: 0, reading: 0, completed: 0, toRead: 0 }; this.myStore.forEach(book => { if (book.collection === 'favorites') counts.favorites++; else if (book.collection === 'reading') counts.reading++; else if (book.collection === 'completed') counts.completed++; else counts.toRead++; }); document.getElementById('favoritesCount').textContent = `${counts.favorites} books`; document.getElementById('readingCount').textContent = `${counts.reading} books`; document.getElementById('completedCount').textContent = `${counts.completed} books`; document.getElementById('toReadCount').textContent = `${counts.toRead} books`; } loadTOC() { const tocContent = document.getElementById('tocContent'); const sampleTOC = [ { title: "Introduction", page: 1, level: 0 }, { title: "Getting Started", page: 15, level: 0 }, { title: "Basic Concepts", page: 25, level: 1 }, { title: "Advanced Features", page: 45, level: 1 }, { title: "Chapter 2: Deep Dive", page: 67, level: 0 }, { title: "Implementation", page: 78, level: 1 }, { title: "Best Practices", page: 95, level: 1 }, { title: "Conclusion", page: 120, level: 0 } ]; tocContent.innerHTML = ''; sampleTOC.forEach(item => { const tocItem = document.createElement('div'); tocItem.className = `toc-item p-2 rounded cursor-pointer hover:bg-gray-100 ${item.level > 0 ? 'ml-4' : ''}`; tocItem.innerHTML = ` <div class="flex justify-between items-center"> <span class="text-sm ${item.level > 0 ? 'text-gray-600' : 'font-medium'}">${item.title}</span> <span class="text-xs text-gray-500">${item.page}</span> </div> `; tocContent.appendChild(tocItem); }); } toggleTOC() { const sidebar = document.getElementById('tocSidebar'); sidebar.classList.toggle('hidden'); } hideTOC() { document.getElementById('tocSidebar').classList.add('hidden'); } toggleAudioBar() { if (this.audioBarVisible) { this.hideAudioBar(); } else { this.showAudioBar(); } } showAudioBar() { document.getElementById('audioBar').classList.add('show'); this.audioBarVisible = true; // Change button icon const toggleBtn = document.getElementById('audioToggleBtn'); toggleBtn.innerHTML = '<i class="fas fa-volume-mute text-xl"></i>'; } hideAudioBar() { document.getElementById('audioBar').classList.remove('show'); this.audioBarVisible = false; // Change button icon const toggleBtn = document.getElementById('audioToggleBtn'); toggleBtn.innerHTML = '<i class="fas fa-volume-up text-xl"></i>'; } initializeAudio() { this.loadVoices(); // Handle voice selection document.getElementById('audioVoice').addEventListener('change', (e) => { this.selectedVoice = this.voices[e.target.value]; }); // Handle speed selection document.getElementById('audioSpeed').addEventListener('change', (e) => { this.audioSpeed = parseFloat(e.target.value); if (this.currentUtterance) { this.currentUtterance.rate = this.audioSpeed; } }); } loadVoices() { this.voices = this.speechSynthesis.getVoices(); const voiceSelect = document.getElementById('audioVoice'); if (this.voices.length === 0) { // Voices not loaded yet, try again setTimeout(() => this.loadVoices(), 100); return; } voiceSelect.innerHTML = ''; this.voices.forEach((voice, index) => { const option = document.createElement('option'); option.value = index; option.textContent = `${voice.name} (${voice.lang})`; voiceSelect.appendChild(option); }); this.selectedVoice = this.voices[0]; this.audioSpeed = 1.0; } playAudio() { const content = document.getElementById('bookContent'); const text = content.textContent || content.innerText; if (this.currentUtterance) { this.speechSynthesis.cancel(); } this.currentUtterance = new SpeechSynthesisUtterance(text); this.currentUtterance.voice = this.selectedVoice; this.currentUtterance.rate = this.audioSpeed || 1.0; this.currentUtterance.pitch = 1.0; this.currentUtterance.volume = 1.0; this.currentUtterance.onstart = () => { this.isPlaying = true; document.getElementById('audioPlayBtn').classList.add('hidden'); document.getElementById('audioPauseBtn').classList.remove('hidden'); this.highlightCurrentText(); }; this.currentUtterance.onend = () => { this.isPlaying = false; document.getElementById('audioPlayBtn').classList.remove('hidden'); document.getElementById('audioPauseBtn').classList.add('hidden'); this.clearHighlight(); }; this.speechSynthesis.speak(this.currentUtterance); } pauseAudio() { if (this.isPlaying) { this.speechSynthesis.pause(); document.getElementById('audioPlayBtn').classList.remove('hidden'); document.getElementById('audioPauseBtn').classList.add('hidden'); this.isPlaying = false; } } stopAudio() { this.speechSynthesis.cancel(); this.isPlaying = false; document.getElementById('audioPlayBtn').classList.remove('hidden'); document.getElementById('audioPauseBtn').classList.add('hidden'); this.clearHighlight(); } highlightCurrentText() { // Simple highlighting simulation const content = document.getElementById('bookContent'); const paragraphs = content.querySelectorAll('p'); paragraphs.forEach((p, index) => { setTimeout(() => { // Remove previous highlights paragraphs.forEach(par => par.classList.remove('highlight')); // Add current highlight p.classList.add('highlight'); }, index * 3000); // 3 seconds per paragraph }); } clearHighlight() { const content = document.getElementById('bookContent'); content.querySelectorAll('.highlight').forEach(el => { el.classList.remove('highlight'); }); } changeFontSize(delta) { this.fontSize += delta; this.fontSize = Math.max(12, Math.min(24, this.fontSize)); const content = document.getElementById('bookContent'); content.style.fontSize = `${this.fontSize}px`; document.getElementById('fontSizeDisplay').textContent = `${this.fontSize}px`; } cycleTheme() { const themes = ['light', 'dark', 'sepia']; const currentIndex = themes.indexOf(this.theme); this.theme = themes[(currentIndex + 1) % themes.length]; this.applyTheme(); } applyTheme() { document.body.className = document.body.className.replace(/theme-\w+/g, ''); if (this.theme !== 'light') { document.body.classList.add(`theme-${this.theme}`); } } showSettingsModal() { document.getElementById('settingsModal').classList.remove('hidden'); } hideSettingsModal() { document.getElementById('settingsModal').classList.add('hidden'); } setupDragDrop() { const dropZone = document.getElementById('dropZone'); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); this.handleFileSelect(e.dataTransfer.files); }); dropZone.addEventListener('click', () => { document.getElementById('fileInput').click(); }); } handleFileSelect(files) { const validFiles = Array.from(files).filter(file => file.type === 'application/epub+zip' || file.name.endsWith('.epub') ); if (validFiles.length === 0) { this.showNotification('Please select valid EPUB files', 'error'); return; } this.importFiles(validFiles); } importFiles(files) { const importProgress = document.getElementById('importProgress'); const importList = document.getElementById('importList'); importProgress.classList.remove('hidden'); importList.innerHTML = ''; files.forEach((file, index) => { const importItem = document.createElement('div'); importItem.className = 'flex items-center justify-between p-3 border rounded-lg'; importItem.innerHTML = ` <div class="flex items-center space-x-3"> <i class="fas fa-file-alt text-blue-500"></i> <div> <p class="font-medium">${file.name}</p> <p class="text-sm text-gray-600">${(file.size / 1024 / 1024).toFixed(2)} MB</p> </div> </div> <div class="flex items-center space-x-2"> <div class="w-32 bg-gray-200 rounded-full h-2"> <div class="bg-blue-500 h-2 rounded-full transition-all" style="width: 0%"></div> </div> <span class="text-sm text-gray-600">0%</span> </div> `; importList.appendChild(importItem); // Simulate import progress this.simulateImport(importItem, file, index); }); } simulateImport(importItem, file, index) { const progressBar = importItem.querySelector('.bg-blue-500'); const progressText = importItem.querySelector('.text-sm.text-gray-600'); let progress = 0; const interval = setInterval(() => { progress += Math.random() * 20; if (progress >= 100) { progress = 100; clearInterval(interval); // Mark as complete progressBar.classList.remove('bg-blue-500'); progressBar.classList.add('bg-green-500'); progressText.textContent = 'Complete'; progressText.classList.remove('text-gray-600'); progressText.classList.add('text-green-600'); // Add to import history this.addToImportHistory(file); this.showNotification(`${file.name} imported successfully!`, 'success'); } else { progressBar.style.width = `${progress}%`; progressText.textContent = `${Math.round(progress)}%`; } }, 200); } addToImportHistory(file) { const historyContainer = document.getElementById('importHistory'); // Remove empty state if it exists const emptyState = historyContainer.querySelector('.text-gray-500'); if (emptyState) { emptyState.remove(); } const historyItem = document.createElement('div'); historyItem.className = 'flex items-center justify-between p-3 border rounded-lg bg-green-50'; historyItem.innerHTML = ` <div class="flex items-center space-x-3"> <i class="fas fa-check-circle text-green-500"></i> <div> <p class="font-medium">${file.name}</p> <p class="text-sm text-gray-600">Imported ${new Date().toLocaleString()}</p> </div> </div> <div class="flex space-x-2"> <button class="px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 transition-all"> Open </button> <button class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 transition-all"> Details </button> </div> `; historyContainer.insertBefore(historyItem, historyContainer.firstChild); } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg text-white ${ type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500' } slide-in`; notification.innerHTML = ` <div class="flex items-center space-x-2"> <i class="fas ${ type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-triangle' : 'fa-info-circle' }"></i> <span>${message}</span> </div> `; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } } // Initialize TieReader when the page loads let tieReader; document.addEventListener('DOMContentLoaded', () => { tieReader = new TieReader(); }); </script> </body> </html> - Initial Deployment
f432f85 verified