From b5829ed773ee82ada213e7f62e0210f62d2afb15 Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Thu, 14 May 2026 21:18:54 +0200 Subject: [PATCH] Remove consolidated reference documentation --- PROTOTYPE_ANALYSIS.md | 311 ---------- TODO.md | 106 ---- references/CLIENT_TODO_v1.md | 145 ----- references/Documentation.md | 441 --------------- references/Plan.md | 0 references/README.md | 48 -- references/SPECIFICATION.md | 598 -------------------- references/STORY_MARKUP.md | 94 ---- references/ai-fiction.js | 1010 --------------------------------- references/game.js | 1034 ---------------------------------- references/index.html | 93 --- references/input-handler.js | 290 ---------- references/localhost.log | 210 ------- references/ui-controller.js | 441 --------------- 14 files changed, 4821 deletions(-) delete mode 100644 PROTOTYPE_ANALYSIS.md delete mode 100644 TODO.md delete mode 100644 references/CLIENT_TODO_v1.md delete mode 100644 references/Documentation.md delete mode 100644 references/Plan.md delete mode 100644 references/README.md delete mode 100644 references/SPECIFICATION.md delete mode 100644 references/STORY_MARKUP.md delete mode 100644 references/ai-fiction.js delete mode 100644 references/game.js delete mode 100644 references/index.html delete mode 100644 references/input-handler.js delete mode 100644 references/localhost.log delete mode 100644 references/ui-controller.js diff --git a/PROTOTYPE_ANALYSIS.md b/PROTOTYPE_ANALYSIS.md deleted file mode 100644 index d02de86..0000000 --- a/PROTOTYPE_ANALYSIS.md +++ /dev/null @@ -1,311 +0,0 @@ -# Prototype Text Pipeline Analysis - -## Overview -The prototype uses a sophisticated text processing pipeline that achieves professional typography through: -1. SmartyPants (typographic punctuation) -2. Hyphenopoly (hyphenation with pipe markers) -3. Knuth-Plass algorithm (optimal line breaking) -4. Precise character-by-character width measurement -5. Justification ratio application - -## Complete Text Flow (Prototype) - -``` -User Text - ↓ -SmartyPants.smartypantsu(text, 1) - ↓ [Converts quotes, dashes to typographic characters] -hyphenator_en(..., '.hyphenatePipe') - ↓ [Inserts | at hyphenation points] -kap(..., measureText, measure.toReversed(), true) - ↓ [Knuth-Plass line breaking] -typesetParagraph(paragraph_data, delay, measure) - ↓ [DOM creation with justification] -Rendered paragraph with proper spacing -``` - -## Key Components - -### 1. Text Preprocessing (game.js:698) - -```javascript -var preview_data = kap( - hyphenator_en( - SmartyPants.smartypantsu(text, 1), - '.hyphenatePipe' // ← CRITICAL: Uses pipe character - ), - measureText, - measure.toReversed(), - true // ← hyphenation enabled -); -``` - -**Purpose**: Creates nodes with accurate widths and hyphenation points - -### 2. Character Width Measurement (game.js:380-406) - -```javascript -function measureText(str) { - // Special cases - if(str.substr(0, 2) == ')/u).forEach(function (fragment) { - if (fragment === ' ') { - // Create glue with stretch/shrink - nodes.push(linebreak.glue(spaceWidth, stretch, shrink)); - } else if (fragment === '|') { - // Create penalty node for hyphenation point - nodes.push(linebreak.penalty(hyphenWidth * 0.25, 100, 1)); - } else { - // Create box node for word - nodes.push(linebreak.box(fragmentWidth, fragment)); - } - }); - - // Run Knuth-Plass algorithm - let breaks = linebreak(nodes, measure, { tolerance: 3, demerits }); - - return { nodes, breaks }; -} -``` - -**Node Types**: -- **box**: Word with fixed width (cannot break) -- **glue**: Space with stretch/shrink (for justification) -- **penalty**: Potential break point (like hyphen) with cost - -### 5. Rendering with Justification (game.js:295-378) - -```javascript -function typesetParagraph(paragraph_data, delay = 0, measure = []) { - // Create paragraph container - let p = document.createElement("p"); - p.style.position = 'relative'; - p.style.height = lineHeight * (paragraph_data.breaks.length - 1) + 'px'; - - // Iterate through lines - for(let i = 1; i < paragraph_data.breaks.length; i++) { - let left = 0; - let ratio = paragraph_data.breaks[i].ratio; // ← JUSTIFICATION RATIO - - // Iterate through nodes on this line - for(let j = paragraph_data.breaks[i-1].position; j <= paragraph_data.breaks[i].position; j++) { - let node = paragraph_data.nodes[j]; - - if(node.type === 'box') { - // Handle hyphenated syllables (lines 316-320) - if(j > paragraph_data.breaks[i-1].position + 1 && - paragraph_data.nodes[j-1].type === 'penalty' && lastChild) { - // Combine with previous syllable - syllable += '\u200c' + node.value; // Zero-width non-joiner - lastChild.innerHTML = syllable; - left += node.width; - } else { - // Create new word span - let word = document.createElement("span"); - word.style.position = 'absolute'; - word.style.top = lineHeight * (i - 1) * 100 / paragraph_height + '%'; - word.style.left = left * 100 / line_width + '%'; - word.innerHTML = node.value; - p.appendChild(word); - left += node.width; - } - } - else if(node.type === 'glue') { - // ← CRITICAL: Apply justification ratio to glue - if(ratio > 0) { - left += node.width + ratio * node.stretch; - } else { - left += node.width + ratio * node.shrink; - } - } - else if(node.type === 'penalty' && node.penalty === 100 && j === breaks[i].position) { - // Add hyphen at line break - let word = document.createElement("span"); - word.innerHTML = "-"; - p.appendChild(word); - } - } - } - - return [p, delay]; -} -``` - -**Key Points**: -1. **Justification**: Glue widths are adjusted by `ratio * stretch` or `ratio * shrink` -2. **Hyphenation**: Syllables after penalty nodes are combined with previous word using zero-width non-joiner -3. **Positioning**: All words use `position: absolute` with percentage-based coordinates - -## Current Implementation Issues - -### Issue 1: Text Processing Pipeline - -**Current** (sentence-queue-module.js:266): -```javascript -const processedText = textProcessor ? await textProcessor.process(text) : text; -``` - -**Problem**: -- `textProcessor.process()` may not pass the correct selector to Hyphenopoly -- Hyphenopoly needs `.hyphenatePipe` selector to use pipe characters - -**Fix Needed**: -```javascript -const processedText = textProcessor ? - await textProcessor.hyphenate( - textProcessor.smartyPants(text), - '.hyphenatePipe' - ) : text; -``` - -### Issue 2: Hyphenation with Pipe Character - -**Current** (text-processor-module.js:275-286): -```javascript -hyphenate(text) { - if (!this.isHyphenationAvailable()) return text; - try { - return this.hyphenator(text); // ← No selector parameter - } catch (error) { - console.error("Error hyphenating text:", error); - return text; - } -} -``` - -**Fix Needed**: Add selector parameter -```javascript -hyphenate(text, selector = null) { - if (!this.isHyphenationAvailable()) return text; - try { - return selector ? - this.hyphenator(text, selector) : - this.hyphenator(text); - } catch (error) { - console.error("Error hyphenating text:", error); - return text; - } -} -``` - -### Issue 3: Knuth-Plass Not Using Pipe Characters - -**Current** (public/js/knuth-and-plass.js): -- May not properly split on pipe characters -- May not create penalty nodes - -**Fix Needed**: Ensure knuth-and-plass.js matches prototype implementation - -### Issue 4: Syllable Combination in Rendering - -**Current** (layout-renderer-module.js): -- Does NOT combine hyphenated syllables -- Missing logic for `if(nodes[j-1].type === 'penalty' && lastChild)` - -**Fix Needed**: Add syllable combination logic when rendering box nodes after penalty nodes - -### Issue 5: Missing #ruler Element - -**Current**: No `#ruler` element for text measurement - -**Fix Needed**: -1. Add `
` to HTML -2. Use ruler for character width measurement in paragraph-layout-module.js - -## Implementation Plan - -### Phase 1: Fix Text Processing Pipeline - -1. **Update text-processor-module.js**: - - Add `selector` parameter to `hyphenate()` method - - Update `process()` to pass `.hyphenatePipe` selector - -2. **Update sentence-queue-module.js**: - - Pass `.hyphenatePipe` selector when calling text processor - - Ensure processedText includes pipe characters - -### Phase 2: Fix Knuth-Plass Integration - -1. **Verify knuth-and-plass.js**: - - Ensure it splits on `\|` character - - Creates `penalty` nodes with cost 100 - - Handles HTML tags properly - -2. **Update paragraph-layout-module.js**: - - Ensure `measureText()` returns 0 for `|` character - - Use `#ruler` element for measurement - - Handle HTML tag stack properly - -### Phase 3: Fix Rendering with Justification - -1. **Update layout-renderer-module.js**: - - Add syllable combination logic for hyphenated words - - Apply justification ratios to glue widths correctly - - Add hyphens at line breaks when penalty node is at break position - -2. **Fix spacing issues**: - - Create space spans with adjusted widths - - Use zero-width non-joiner for syllable combination - -### Phase 4: Testing & Refinement - -1. **Test with simple text**: "This is a test." -2. **Test with hyphenation**: Long words that span lines -3. **Test with justification**: Full paragraphs -4. **Test with special characters**: Quotes, dashes, etc. - -## Success Criteria - -✅ SmartyPants converts quotes correctly -✅ Hyphenopoly inserts pipe characters -✅ Knuth-Plass creates proper breaks with hyphenation -✅ Words don't overlap -✅ Words have proper spacing (not smushed) -✅ Justification works (even spacing across line width) -✅ Hyphens appear at line breaks -✅ Drop caps and indentation work diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4fff76b..0000000 --- a/TODO.md +++ /dev/null @@ -1,106 +0,0 @@ -# Module System Refactoring TODO - -## High Priority (Critical Architectural Issues) - -### 1. Asynchronous Flow Control Improvements -- [ ] Remove all `setTimeout` calls used for synchronization in modules - - [X] Replace timeout in `browser-tts-handler.js` with proper Promise handling for voice loading - - [X] Eliminate race condition in `tts-player.js` that uses a hard-coded 1000ms timeout - - [ ] Remove all `setTimeout` calls in `ui-controller.js` for UI updates -- [ ] Implement proper Promise-based flow control in all modules - - [ ] Update `kokoro-handler.js` to correctly handle loading events - - [ ] Ensure all `async/await` patterns follow best practices - - [ ] Fix race conditions in module loading sequences - -### 2. Module State Management -- [ ] Fix premature reporting of `FINISHED` state - - [ ] Ensure `tts-player.js` properly waits for Kokoro loading before reporting FINISHED - - [ ] Add proper state checks in all modules before reporting FINISHED -- [ ] Implement proper state transition reporting - - [ ] Update modules to use event system for reporting state transitions - - [ ] Add better error handling during module initialization - -### 3. Module Dependencies & Loading -- [ ] Fix missing dependency declarations - - [ ] Update `ui-controller.js` to properly declare its TTS dependency - - [ ] Ensure all modules correctly specify all dependencies -- [ ] Remove dependency availability checks within modules - - [ ] Remove conditional checks like `if (!this.ttsHandler)` in `ui-controller.js` - - [ ] Rely on the module loader for dependency management - -## Medium Priority (Functionality & Implementation Issues) - -### 4. TTS Handler Implementation -- [ ] Implement missing `tts-handler.js` file content - - [ ] Create proper implementation with consistent interface - - [ ] Ensure it uses proper event-based communication -- [ ] Fix inconsistent event usage across TTS handlers - - [ ] Replace direct callbacks with event system - - [ ] Standardize event names and parameters - -### 5. Animation Queue Enhancements -- [ ] Implement proper queue control mechanisms - - [ ] Add pause/resume functionality - - [ ] Implement more robust animation timing - - [ ] Add priority management for animations - -### 6. UI Controller Cleanup -- [ ] Fix duplicate methods in UI Controller - - [ ] Deduplicate code for creating UI elements - - [ ] Consolidate event handling functions -- [ ] Remove redundant `ModuleEvent` class implementation - - [ ] Use the shared implementation from `base-module.js` - -### 7. Kokoro Loading Implementation -- [ ] Implement proper `requestIdleCallback` for Kokoro loading - - [ ] Follow the pattern described in the specification - - [ ] Add progress reporting during Kokoro loading -- [ ] Fix event handling for loading completion - -## Lower Priority (Refinements & Optimizations) - -### 8. Code Quality & Consistency -- [ ] Standardize module registration pattern - - [ ] Ensure all modules follow the same pattern - - [ ] Fix inconsistencies in export approaches -- [ ] Improve module progress reporting - - [ ] Make progress reporting more granular - - [ ] Add more descriptive status messages - -### 9. Error Handling Improvements -- [ ] Add better error recovery mechanisms - - [ ] Implement fallbacks for critical failures - - [ ] Add user-friendly error messages -- [ ] Improve error logging - - [ ] Add structured error reporting - - [ ] Implement debugging tools - -### 10. Performance Optimizations -- [ ] Optimize module loading sequence - - [ ] Prioritize critical modules - - [ ] Defer non-essential loading -- [ ] Improve resource utilization - - [ ] Minimize memory footprint - - [ ] Reduce CPU usage during animations - -## Documentation & Testing - -### 11. Documentation -- [ ] Add JSDoc comments to all public methods -- [ ] Create architectural documentation - - [ ] Document module dependencies - - [ ] Explain event system -- [ ] Add example usage for modules - -### 12. Testing -- [ ] Create unit tests for modules -- [ ] Implement integration tests for module system -- [ ] Add browser compatibility tests - -## Future Enhancements - -### 13. New Features -- [ ] Add module versioning support -- [ ] Implement module hot-reloading -- [ ] Create plugin system for extending modules -- [ ] Add internationalization support for UI \ No newline at end of file diff --git a/references/CLIENT_TODO_v1.md b/references/CLIENT_TODO_v1.md deleted file mode 100644 index 730a564..0000000 --- a/references/CLIENT_TODO_v1.md +++ /dev/null @@ -1,145 +0,0 @@ -# Client Architecture Refactoring TODO List - -## Enhanced SentenceQueueModule Implementation - -### Phase 1: Core Structure and Design - -1. **Design the Sentence Object Structure** - - [ ] Define comprehensive sentence object with fields for: - - Unique ID - - Original text - - Processed text (hyphenated, typeset) - - Layout information (breaks, nodes, typography) - - Audio component (player, duration, data, type) - - Status tracking (pending, processing, ready, playing, complete) - -2. **Implement Basic Queue Management** - - [ ] Create methods for adding sentences to the queue - - [ ] Implement queue processing logic that maintains order - - [ ] Add status tracking for each sentence in the queue - - [ ] Implement priority handling for urgent sentences - -### Phase 2: Text Processing Integration - -1. **Integrate with Paragraph Layout** - - [ ] Connect to ParagraphLayoutModule for text processing - - [ ] Implement hyphenation and typesetting in the queue - - [ ] Store layout information in the sentence object - - [ ] Ensure layout processing happens in parallel with audio - -2. **Text Animation Preparation** - - [ ] Calculate animation timing based on text length and settings - - [ ] Prepare animation data for each word in the sentence - - [ ] Store animation timing in the sentence object - - [ ] Create animation player function for the sentence - -### Phase 3: Audio Processing Integration - -1. **TTS System Integration** - - [ ] Implement audio generation for Kokoro TTS - - [ ] Implement browser TTS handling with duration estimation - - [ ] Implement "none" TTS option with duration calculation - - [ ] Create consistent player interface for all TTS types - -2. **Audio Data Management** - - [ ] Implement audio data storage in sentence objects - - [ ] Connect with TTSFactoryModule's IndexedDB for persistent caching - - [ ] Add audio preloading capabilities - - [ ] Implement audio resource cleanup - -### Phase 4: Playback Coordination - -1. **Synchronized Playback** - - [ ] Implement coordinated text animation and audio playback - - [ ] Create timing adjustment based on speed settings - - [ ] Add event handling for playback states (start, pause, resume, complete) - - [ ] Implement sentence transition handling - -2. **User Interaction Handling** - - [ ] Add support for fast-forwarding text/audio - - [ ] Implement pause/resume functionality - - [ ] Handle user interruptions gracefully - - [ ] Support skipping to next sentence - -## Component Consolidation - -### Phase 1: Identify and Remove Redundancies - -1. **TTSPlayerModule Refactoring** - - [ ] Remove preloadedAudio Map (replaced by sentence objects) - - [ ] Remove preloadQueue (replaced by SentenceQueue) - - [ ] Update speak method to use SentenceQueue - - [ ] Refactor to be a thin wrapper around SentenceQueue - -2. **UIDisplayHandlerModule Refactoring** - - [ ] Remove pendingParagraphs queue (replaced by SentenceQueue) - - [ ] Update displayText to use SentenceQueue - - [ ] Modify processNextParagraph to work with sentence objects - - [ ] Update event handling to work with the new architecture - -3. **KokoroTTSModule Refactoring** - - [ ] Replace pendingGenerations Map with SentenceQueue integration - - [ ] Update generateSpeech to work with sentence objects - - [ ] Modify iframe communication to support the new structure - - [ ] Ensure backward compatibility during transition - -4. **TextBufferModule Refactoring** - - [ ] Move sentence preparation logic to SentenceQueue - - [ ] Update text handling to work with the new architecture - - [ ] Ensure proper integration with SentenceQueue - - [ ] Maintain high-level text management responsibilities - -### Phase 2: Interface Updates - -1. **Update Module Interfaces** - - [ ] Create consistent interfaces for interacting with SentenceQueue - - [ ] Update event system to work with sentence objects - - [ ] Implement progress reporting for sentence processing - - [ ] Add debugging and monitoring capabilities - -2. **Documentation and Examples** - - [ ] Document the new architecture and interfaces - - [ ] Create usage examples for common scenarios - - [ ] Update developer guidelines - - [ ] Add migration guide for existing code - -## Testing and Validation - -1. **Unit Testing** - - [ ] Create tests for SentenceQueue core functionality - - [ ] Test text processing integration - - [ ] Test audio processing integration - - [ ] Test playback coordination - -2. **Integration Testing** - - [ ] Test interaction between SentenceQueue and other modules - - [ ] Validate timing and synchronization - - [ ] Test error handling and recovery - - [ ] Verify performance under load - -3. **User Experience Testing** - - [ ] Validate text animation quality - - [ ] Test audio playback quality - - [ ] Verify synchronization from user perspective - - [ ] Test accessibility features - -## Implementation Strategy - -1. **Phased Rollout** - - [ ] Implement SentenceQueue core structure - - [ ] Add text processing integration - - [ ] Add audio processing integration - - [ ] Implement playback coordination - - [ ] Gradually replace existing components - -2. **Backward Compatibility** - - [ ] Maintain support for existing interfaces during transition - - [ ] Implement adapter patterns where needed - - [ ] Add feature flags for enabling/disabling new architecture - - [ ] Create fallback mechanisms for error recovery - -3. **Performance Optimization** - - [ ] Implement parallel processing where possible - - [ ] Optimize memory usage for sentence objects - - [ ] Add resource management for audio data - - [ ] Implement efficient queue processing algorithms \ No newline at end of file diff --git a/references/Documentation.md b/references/Documentation.md deleted file mode 100644 index dbcf6ef..0000000 --- a/references/Documentation.md +++ /dev/null @@ -1,441 +0,0 @@ -# AI Interactive Fiction Engine - Technical Documentation - -## 1. Overview - -This document provides a comprehensive technical explanation of the AI Interactive Fiction Engine implemented in `game.js`. The engine leverages inkjs for narrative management and incorporates advanced text rendering features, including custom animations, sophisticated typography using SmartyPants and Hyphenopoly, and optimal line breaking via the Knuth and Plass algorithm, to create rich, dynamic interactive fiction experiences. - -## 2. Core Technologies - -The engine is built upon several key technologies: - -* **inkjs**: Manages the core interactive fiction logic, including story state, branching narrative, choices, and variables. -* **Knuth and Plass Line Breaking Algorithm**: Adapted implementation for optimal paragraph justification and line breaking, ensuring high-quality text layout. (Based on Bram Stein's "typeset" library). -* **Hyphenopoly**: Provides high-quality, language-aware text hyphenation, crucial for justified text layout. -* **SmartyPants**: Enhances typography by converting plain text elements (quotes, dashes) into their typographically correct equivalents. -* **Custom Animation System**: A queue-based system handles the precise timing and visual reveal of text elements. -* **JavaScript (ES6+)**: The primary implementation language. -* **HTML/CSS**: Used for structuring the UI and styling the visual presentation. - -## 3. Engine Architecture and Flow - -The engine follows a well-defined process from initialization to interactive playback. - -### 3.1. Initialization (`window.onload`) - -Execution begins when the window loads: - -1. **Initial State Setup**: Global state variables (`running`, `fastForwardingAll`, `speech`, `speed`, `delay`) are initialized. -2. **Background Effects**: Lighting animations for background visuals are configured. -3. **Text Libraries**: Hyphenopoly is initialized for hyphenation support. -4. **Story Loading**: The compiled Ink story (JSON format) is fetched and loaded. -5. **Event Listeners**: Listeners for user interactions (keyboard input, clicks, slider changes) are attached. -6. **Playback Start**: The `continueStory()` function is called to begin rendering the narrative. - -### 3.2. The Main Playback Loop (`continueStory()`) - -The `continueStory()` function is the engine's core, orchestrating the display of narrative content and handling user choices. - -**Step-by-step Process:** - -1. **Setup**: Animation state variables (`fade_in`, `running`, `fastForwardingAll`) are reset for the current turn. -2. **Story Content Iteration**: - ```javascript - while(story.canContinue) { - // Fetch next paragraph & associated tags from inkjs - var paragraphText = story.Continue(); - var tags = story.currentTags; - - // Process tags (Section 3.3) - // ... - - // Process and render text (Section 4) - // ... - } - ``` - The loop continues as long as inkjs indicates more content is available for the current narrative sequence. - -3. **Choice Handling**: After exhausting the `story.canContinue` content, the engine checks for available choices: - ```javascript - story.currentChoices.forEach(function(choice) { - // Create interactive DOM elements for each choice - // Attach event listeners to handle choice selection - // Apply appropriate styling - }); - ``` - When a user selects a choice, an event handler calls `story.ChooseChoiceIndex(choice.index)` and then triggers `continueStory()` again to display the subsequent narrative branch. - -4. **End of Story**: If `story.canContinue` is false and there are no choices, the story concludes or reaches a waiting point. - -### 3.3. Tag Processing - -Ink tags are used to control various non-textual aspects of the presentation: - -* `AUDIO `: Triggers playback of an audio file. -* `IMAGE `: Displays an image. -* `BACKGROUND `: Changes the background style. -* `CHAPTER`: Applies special formatting for chapter headings (often including drop caps). -* `SEPARATOR`: Inserts a decorative visual separator element. -* Custom CSS Classes: Tags can directly add CSS classes to paragraph elements for specific styling. - -Tags are processed within the `continueStory` loop before the associated paragraph text is rendered. - -## 4. Advanced Text Rendering Pipeline - -A key feature of this engine is its sophisticated text rendering pipeline, designed to produce professional-quality typography and layout. - -### 4.1. Overview - -Text rendering involves several stages for each paragraph: - -1. **Preprocessing**: Apply SmartyPants for typographic correctness and Hyphenopoly for hyphenation points. -2. **Line Breaking Calculation**: Use the Knuth and Plass algorithm (`kap` function) to determine optimal line breaks for the entire paragraph. -3. **Typesetting and DOM Creation**: Use the `typesetParagraph` function to generate and position DOM elements based on the line break data. -4. **Animation Scheduling**: Schedule the appearance of each text fragment using the custom animation system. - -### 4.2. Knuth and Plass Line Breaking - -The engine employs an adaptation of the Knuth and Plass line breaking algorithm to achieve superior text justification compared to standard browser methods. - -#### 4.2.1. Origin and Adaptation - -* The core logic is based on [Bram Stein's "typeset" library](https://github.com/bramstein/typeset). -* **Key Adaptations**: - * **HTML Tag Support**: Introduced `linebreak.tag()` nodes to preserve inline HTML formatting (e.g., ``, ``, custom spans) during line breaking. - * **Punctuation Handling**: Implemented custom logic for refined spacing around punctuation marks (`.,:;!?`), splitting width into symbolic (25%) and spacing (75%) components. - * **Hyphenation Integration**: Tightly integrated with Hyphenopoly, treating hyphenation points as potential breaks with a specific, reduced penalty (25% of normal hyphen width). - -#### 4.2.2. Algorithm Principles - -* **Optimization Problem**: Treats line breaking as minimizing the overall "badness" (demerits) of a paragraph's layout. -* **Global View**: Considers the entire paragraph at once, unlike greedy algorithms. -* **Box, Glue, Penalty Model**: Represents text as: - * **Boxes**: Unbreakable units (words, tagged elements). - * **Glue**: Flexible spaces that can stretch or shrink. - * **Penalties**: Opportunities for line breaks (e.g., after spaces, hyphens), with associated costs (demerits). -* **Demerits System**: Assigns penalties based on line tightness, hyphenation, and relationships between consecutive lines (fitness classes). -* **Optimal Path**: Finds the sequence of line breaks with the lowest total demerit score. - -#### 4.2.3. Implementation (`knuth-and-plass.js`, `linebreak.js`) - -* **`knuth-and-plass.js` (Adapter Layer)**: - * Provides the `kap(text, measureText, measure, hyphenation)` function. - * Parses the input text (already processed by SmartyPants/Hyphenopoly) into Box, Glue, Penalty, and Tag nodes. - * Handles special spacing rules for punctuation. - * Interfaces with the core `linebreak.js` module. -* **`linebreak.js` (Core Algorithm)**: - * Contains the `linebreak(nodes, lines, settings)` function. - * Implements the main algorithm logic: ratio calculation, demerit calculation, fitness class tracking, active node management (using the LinkedList), and path finding. - * Defines node types: `linebreak.box()`, `linebreak.glue()`, `linebreak.penalty()`, `linebreak.tag()`. -* **Customization Parameters**: - * `tolerance`: Controls acceptable line stretch/shrink (default: 2). - * `demerits`: Configurable penalties for lines, flagged breaks (e.g., consecutive hyphens), and fitness class mismatches. - -#### 4.2.4. Supporting Data Structure: LinkedList (`linked-list.js`) - -* A custom, modern ES6 implementation of a doubly linked list, derived from the `typeset` library. -* **Purpose**: Efficiently manages the "active nodes" (potential breakpoints being considered) within the `linebreak.js` algorithm. Critical for performance. -* **Features**: ES6 class syntax, getter methods (`size`, `first`), method chaining. -* **Known Bug**: The `get last()` method incorrectly references `this.last` instead of `this.tail`, potentially causing infinite recursion if called. **This should be corrected to `return this.tail;`**. - -#### 4.2.5. Advantages - -* Superior justification and word spacing. -* Reduced and more aesthetically placed hyphenation. -* Avoids jarringly tight or loose lines. -* Produces professional, book-like typography. -* Preserves rich text formatting during layout optimization. - -### 4.3. Typography Enhancements (SmartyPants) - -* Before line breaking, text is processed by `SmartyPants.smartypantsu(text, 1)`. -* This converts: - * Straight quotes (`"`, `'`) to curly quotes (“ ”, ‘ ’). - * Double hyphens (`--`) to em-dashes (—). - * Triple hyphens (`---`) to em-dashes (—) (configurable). - * Three periods (`...`) to ellipses (…). - -### 4.4. Hyphenation (Hyphenopoly) - -* Applied after SmartyPants using `hyphenator_en(text, '.hyphenatePipe')`. -* Inserts soft hyphen characters (`­` or similar markers) at valid break points within words, based on linguistic rules. -* These potential hyphenation points are then treated as `Penalty` nodes by the line-breaking algorithm, allowing for more flexible justification. - -### 4.5. Typesetting and DOM Rendering (`typesetParagraph()`) - -This function translates the calculated layout data from `kap` into visible, animated DOM elements. - -```javascript -function typesetParagraph(paragraph_data, delay = 0, measure = []) { - // ... setup paragraph element

... - - // Iterate through lines defined by paragraph_data.breaks - for(let i = 1; i < paragraph_data.breaks.length; i++) { - // Iterate through nodes (words, spaces, hyphens, tags) within the line - for(let j = paragraph_data.breaks[i-1].position; j <= paragraph_data.breaks[i].position; j++) { - const node = paragraph_data.nodes[j]; - // Create appropriate DOM element (e.g., for words/tags) - // Set absolute position (left, top) based on calculated widths and line height - // Apply styles (opacity: 0 initially) - // Schedule fade-in animation using scheduleTimeout() (Section 5) - // Update the running 'delay' total for the next element - } - // Handle line breaks, adjusting vertical position - } - - return [p, delay]; // Return the populated

element and the final delay value -} -``` - -* Each word, space, or preserved tag becomes a separate, absolutely positioned DOM element (typically a ``). -* Positions are determined precisely based on the widths calculated during the line-breaking phase. -* Initial opacity is set to 0, and fade-in animations are scheduled sequentially. - -## 5. Animation System - -The engine features a sophisticated animation system for revealing text dynamically. - -### 5.1. Animation Queue (`timeoutQueue`) - -* A central array `timeoutQueue = []` tracks all pending `setTimeout` calls. -* Each entry is an object storing the function to execute, its arguments, and the `timeoutId`. - -### 5.2. Scheduling Animations (`scheduleTimeout`) - -```javascript -function scheduleTimeout(func, delay, ...args) { - // Creates timeoutObject with execute method and null timeoutId - // Uses setTimeout to schedule execution after 'delay' - // Inside setTimeout callback: executes func, removes object from timeoutQueue - // Pushes timeoutObject onto timeoutQueue - // Returns the timeoutId -} -``` -* Provides a managed way to schedule functions, ensuring they are tracked. -* Enables batch operations like fast-forwarding or cancellation. - -### 5.3. Timing and Delay (`window.delay`, `window.speed`) - -* `window.delay`: Accumulates the total delay time as elements are scheduled. Each new element's animation starts after the previous one finishes. -* `window.speed`: Controls the duration of each word's fade-in, typically adjusted by a UI slider. The delay increment for a word is often calculated like `delay += window.speed * word.length;`. -* Speed Adjustment: The UI slider uses a non-linear function (`Math.pow(100.0 - value, 3) / 10000 * 10 + 0.01`) for finer control at slower speeds. - -### 5.4. Fast Forwarding (`fastForward`, `fastForwardingAll`) - -* The `fastForward()` function: - 1. Iterates through `timeoutQueue`. - 2. Calls `clearTimeout()` for each pending animation. - 3. Immediately executes the scheduled function (`timeoutObject.execute()`). - 4. Clears the `timeoutQueue`. - 5. Resets `window.delay` to 0. -* Triggered by user actions (spacebar, click) or programmatically. -* `window.fastForwardingAll` flag indicates if fast-forward is continuously active (e.g., holding space). -* Visual Feedback: The page border changes color (e.g., red) when `fastForwardingAll` is true. - -### 5.5. Animation Interruption (`stopAllTimeouts`) - -* The `stopAllTimeouts()` function: - 1. Iterates through `timeoutQueue`. - 2. Calls `clearTimeout()` for each pending animation. - 3. Clears the `timeoutQueue`. -* Used during navigation, loading saves, or story resets to prevent outdated animations from playing. - -## 6. Additional Features - -### 6.1. Text-to-Speech (TTS) - -* Integrates with an external TTS service (e.g., ElevenLabs API). -* A UI button toggles speech playback. -* Attempts to synchronize audio playback with text animation reveal (implementation details not fully specified in the source document). -* Playback can be interrupted by user interactions or fast-forwarding. - -### 6.2. State Management and Persistence - -* **Ink State**: The core narrative state (current position, variable values, etc.) is managed by inkjs and can be serialized using `story.state.toJson()`. -* **Local Storage**: Used to persist the serialized Ink state and potentially other UI/game states (like rendered history) across browser sessions. -* **Save/Load**: Functionality allows users to explicitly save the current state and reload it later. This involves restoring the Ink state via `story.state.loadJson()` and potentially re-rendering the story history up to that point. - -## 7. External Libraries & Credits - -The engine relies on the following external libraries: - -* **inkjs** - * **Author**: Yannick Lohse (y-lohse) - * **Source**: [https://github.com/y-lohse/inkjs](https://github.com/y-lohse/inkjs) - * **Description**: JavaScript port of Inkle's Ink narrative scripting language. -* **Hyphenopoly** - * **Author**: Hermann Monnich (mnater) - * **Source**: [https://github.com/mnater/Hyphenopoly](https://github.com/mnater/Hyphenopoly) - * **Website**: [https://mnater.github.io/Hyphenopoly/](https://mnater.github.io/Hyphenopoly/) - * **Description**: High-quality JavaScript hyphenation library. -* **SmartyPants** - * **Original Author**: John Gruber - * **JavaScript Port Author**: Example: Othree (othree) - * **Source (Example Port)**: [https://github.com/othree/smartypants.js](https://github.com/othree/smartypants.js) - * **Description**: Typography prettifier for quotes, dashes, etc. -* **typeset (Basis for Knuth & Plass / LinkedList)** - * **Author**: Bram Stein (bramstein) - * **Source**: [https://github.com/bramstein/typeset](https://github.com/bramstein/typeset) - * **Description**: Original JavaScript implementation of the Knuth-Plass line breaking algorithm, adapted for this engine. - -## 8. Practical Refactoring Recommendations (Updated) - -Based on the detailed analysis of the engine's functionality and the goal of optimizing for **modularity, separation of concerns, and reusability**, the following refined refactoring strategy is recommended. This approach breaks the system into highly focused, potentially reusable components, moving away from reliance on global state and tightly coupled logic. - -### Core Component Modules: - -1. **`AnimationQueue` (`animation-queue.js`)** - * **Responsibility**: Solely manage the timing and execution queue for all scheduled animations (primarily text reveal). - * **Core Functions**: `schedule(func, delay, ...args)`, `fastForward()`, `stop()`, `setSpeed(value)`. - * **State**: Manages the internal `queue` array, the current `delay` accumulator, and the animation `speed`. - * **Reusability**: Highly reusable for any system requiring sequenced, timed execution with speed control and interruption. - -2. **`TextProcessor` (`text-processor.js`)** - * **Responsibility**: Encapsulate text pre-processing steps required before layout calculation. - * **Core Functions**: `process(text)` method that applies typographic enhancements (SmartyPants) and hyphenation (Hyphenopoly). - * **Dependencies**: Takes instances or functions of the SmartyPants and Hyphenopoly libraries during construction. - * **Reusability**: Reusable wherever this specific text processing pipeline (SmartyPants + Hyphenopoly) is needed. - -3. **`ParagraphLayout` (`paragraph-layout.js`)** - * **Responsibility**: Interface with the Knuth-Plass line breaking algorithm (`kap` function) to calculate optimal line breaks. - * **Core Functions**: `calculateLayout(processedText, measures)` method. - * **Dependencies**: Takes the `kap` function and a `measureText` function (capable of measuring text widths in the target rendering context) during construction. - * **Reusability**: Reusable in any system needing high-quality paragraph line breaking, provided the `kap` algorithm and a text measurement function are supplied. - -4. **`LayoutRenderer` (`layout-renderer.js`)** - * **Responsibility**: Translate the abstract layout data (from `ParagraphLayout`) into concrete visual elements (DOM nodes in this case). Handle visual tag rendering. - * **Core Functions**: `renderParagraph(layoutData, measures)` creates positioned DOM elements (e.g., `` for words/tags), applies styles (initial opacity 0), and *uses* `AnimationQueue.schedule` to initiate fade-in animations. `renderVisualTag(tagType, tagData)` handles rendering for `IMAGE`, `BACKGROUND`, `CHAPTER`, `SEPARATOR`, and applying CSS classes. - * **Dependencies**: Takes an `AnimationQueue` instance and potentially configuration for visual elements. - * **Reusability**: Moderately reusable. The core logic of translating layout data is reusable, but the specific DOM creation part is tied to HTML/DOM rendering. Could be adapted for other rendering targets (e.g., Canvas). - -5. **`AudioManager` (`audio-manager.js`)** - * **Responsibility**: Manage loading and playback of non-TTS audio effects triggered by tags (e.g., `AUDIO `). - * **Core Functions**: `loadSound(id, url)`, `playSound(id)`, `stopSound(id)`, `stopAllSounds()`. - * **Reusability**: Highly reusable component for basic audio management in web applications. - -6. **`TtsPlayer` (`tts-player.js`)** - * **Responsibility**: Handle interactions with the Text-to-Speech service (e.g., ElevenLabs API), manage playback, and potentially synchronize with the `AnimationQueue`. - * **Core Functions**: `speak(text)`, `stopSpeaking()`, `setVoice(voiceId)`, `configure(apiKey, ...)`. - * **Dependencies**: May need the `AnimationQueue` for synchronization if required. - * **Reusability**: Reusable for adding TTS functionality, specific implementation depends on the chosen TTS provider API. - -7. **`PersistenceManager` (`persistence-manager.js`)** - * **Responsibility**: Handle saving and loading the game state. - * **Core Functions**: `saveState(stateObject)`, `loadState()`. `stateObject` would contain Ink state JSON, potentially UI state, scroll position, etc. - * **Dependencies**: Configurable storage backend (e.g., `localStorage` wrapper). - * **Reusability**: Highly reusable for saving/loading application state to various storage mechanisms. - -8. **`InkStoryPlayer` (`ink-story-player.js`)** - * **Responsibility**: Orchestrate the narrative flow specific to the Ink story. Manage the `inkjs.Story` instance. - * **Core Functions**: `loadStory(storyContentJson)`, `continueStory(containerElement)`, `chooseChoice(index)`. It drives the `story.Continue()` loop, gets text and tags. It *delegates* tasks: - * Text processing -> `TextProcessor` - * Layout calculation -> `ParagraphLayout` - * Rendering paragraphs/visuals -> `LayoutRenderer` - * Handling `AUDIO` tags -> `AudioManager` - * Handling speech requests (if implemented via tags) -> `TtsPlayer` - * Saving/Loading -> `PersistenceManager` - * **Dependencies**: `inkjs.Story` class, `TextProcessor`, `ParagraphLayout`, `LayoutRenderer`, `AudioManager`, `TtsPlayer`, `PersistenceManager`. - * **Reusability**: Specific to using Ink narratives but uses reusable components for its operations. - -9. **`UiController` (`ui-controller.js`)** - * **Responsibility**: Manage user interface interactions (event listeners) and update UI elements (sliders, buttons, status indicators). - * **Core Functions**: `setupEventListeners()`, methods to handle specific events (e.g., `handleSpeedChange`, `handleFastForwardToggle`, `handleChoiceClick`, `handleSaveClick`, `handleLoadClick`, `handleTtsToggle`). - * **Dependencies**: Interacts primarily with `InkStoryPlayer` (e.g., to choose choice, trigger save/load), `AnimationQueue` (to set speed, trigger fast-forward), `TtsPlayer` (to toggle speech), `AudioManager` (potentially to control master volume). - * **Reusability**: Specific to the application's UI structure but follows a standard controller pattern. - -### Main Application Integration (`animated-fiction.js` or `main.js`): - -```javascript -// main.js (Example Setup) -import { AnimationQueue } from './animation-queue.js'; -import { TextProcessor } from './text-processor.js'; -// ... import other modules ... -import { Story as InkStory } from './ink.js'; // Assuming inkjs is available - -class AnimatedFictionApp { - constructor(config) { - this.config = config; // Story content URL, container element, API keys, etc. - this.storyContent = null; // Loaded later - - // 1. Instantiate Core Components - this.animationQueue = new AnimationQueue(); - // Provide external libraries/functions to components that need them - this.textProcessor = new TextProcessor(SmartyPants, hyphenator_en); // Assuming these are globally available or imported - this.paragraphLayout = new ParagraphLayout(kap, this.measureDomText.bind(this)); // `kap` algorithm, bind measure func - this.layoutRenderer = new LayoutRenderer(this.animationQueue); - this.audioManager = new AudioManager(); - this.ttsPlayer = new TtsPlayer({ apiKey: config.ttsApiKey /*, optional animationQueue */ }); - this.persistenceManager = new PersistenceManager({ storage: localStorage }); // Configure storage backend - - // 2. Instantiate Orchestrator & UI - this.storyPlayer = new InkStoryPlayer({ - InkStory: InkStory, // Pass the inkjs Story constructor - textProcessor: this.textProcessor, - paragraphLayout: this.paragraphLayout, - layoutRenderer: this.layoutRenderer, - audioManager: this.audioManager, - ttsPlayer: this.ttsPlayer, - persistenceManager: this.persistenceManager, - }); - this.uiController = new UiController({ - storyPlayer: this.storyPlayer, - animationQueue: this.animationQueue, - ttsPlayer: this.ttsPlayer, - // Pass references to DOM elements (buttons, sliders) - speedSliderElement: document.getElementById('speedSlider'), - choiceContainerElement: document.getElementById('choices'), - // ... other elements - }); - } - - // Method needed by ParagraphLayout, tied to rendering context - measureDomText(text, style = '') { - // Implementation to measure text width in the DOM - // (e.g., create temporary span, apply style, measure offsetWidth) - // ... return width ... - } - - async load() { - // Fetch story content, etc. - const response = await fetch(this.config.storyUrl); - this.storyContent = await response.json(); - } - - start() { - if (!this.storyContent) throw new Error("Story not loaded"); - - // Setup initial states, load saved game if applicable - const savedState = this.persistenceManager.loadState(); - const initialInkState = savedState ? savedState.inkJson : null; - - this.storyPlayer.loadStory(this.storyContent, initialInkState); - this.uiController.setupEventListeners(); - // Set initial speed from config or saved state - this.animationQueue.setSpeed(this.config.initialSpeed || 0.05); - - // Begin the story - this.storyPlayer.continueStory(document.getElementById(this.config.storyContainerId)); - } -} - -// Initialize and start the application -window.onload = async () => { - const app = new AnimatedFictionApp({ - storyUrl: 'story.json', - storyContainerId: 'story', - ttsApiKey: 'YOUR_API_KEY' // Example config - // ... other configurations - }); - await app.load(); - app.start(); -}; -``` - -### Benefits of this Refined Structure: - -* **High Modularity**: Each class has a single, well-defined responsibility. -* **Improved Reusability**: Components like `AnimationQueue`, `TextProcessor`, `ParagraphLayout`, `AudioManager`, `TtsPlayer`, `PersistenceManager` have minimal dependencies on the specific interactive fiction context and can be reused elsewhere. -* **Clear Separation of Concerns**: Narrative logic (`InkStoryPlayer`) is separated from rendering (`LayoutRenderer`), timing (`AnimationQueue`), text manipulation (`TextProcessor`), UI (`UiController`), and side effects (Audio/TTS/Persistence). -* **Testability**: Individual components can be unit-tested more easily by mocking their dependencies. -* **Maintainability**: Changes within one module are less likely to impact others. Replacing a component (e.g., swapping the TTS provider) becomes more manageable. - -This refactoring focuses squarely on creating independent, reusable building blocks, aligning perfectly with the goals specified. \ No newline at end of file diff --git a/references/Plan.md b/references/Plan.md deleted file mode 100644 index e69de29..0000000 diff --git a/references/README.md b/references/README.md deleted file mode 100644 index cdacc3e..0000000 --- a/references/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Animated Fiction Engine - Modular Refactoring - -This directory contains the refactored, modular version of the interactive fiction engine, previously implemented monolithically in `references/game.js`. The refactoring follows the recommendations outlined in Chapter 8 of `references/Documentation.md`. - -## Overview - -The engine is now broken down into several distinct, reusable components, each with a specific responsibility. This improves maintainability, testability, and allows for easier integration into different projects or replacement of individual components. - -## Modules - -The core modules are: - -1. **`animation-queue.js` (`AnimationQueue`)**: Manages the timing and execution queue for animations, primarily text reveal. Handles scheduling, fast-forwarding, stopping, and speed control. -2. **`text-processor.js` (`TextProcessor`)**: Encapsulates text pre-processing steps (SmartyPants for typography, Hyphenopoly for hyphenation). -3. **`paragraph-layout.js` (`ParagraphLayout`)**: Interfaces with the Knuth-Plass line breaking algorithm (`kap` function) to calculate optimal paragraph layouts. Requires a text measurement function. -4. **`layout-renderer.js` (`LayoutRenderer`)**: Translates the calculated layout data into DOM elements, handles visual tag rendering (images, backgrounds, etc.), and schedules animations using the `AnimationQueue`. -5. **`audio-manager.js` (`AudioManager`)**: Manages loading and playback of non-TTS audio effects triggered by Ink tags (`AUDIO`, `AUDIOLOOP`). -6. **`tts-player.js` (`TtsPlayer`)**: Handles interactions with Text-to-Speech services (using `tts-factory.js` for selection) and manages TTS playback. -7. **`persistence-manager.js` (`PersistenceManager`)**: Handles saving and loading the game state (Ink state JSON and rendered history) using a configurable storage backend (defaulting to `localStorage`). -8. **`ink-story-player.js` (`InkStoryPlayer`)**: Orchestrates the Ink narrative flow. Manages the `inkjs.Story` instance, processes story content and tags, and delegates tasks to other modules (text processing, layout, rendering, audio, TTS, persistence). -9. **`ui-controller.js` (`UiController`)**: Manages user interface interactions (buttons, sliders, keyboard shortcuts) and updates UI elements. Interacts with other modules to trigger actions (e.g., change speed, save/load, choose choice). -10. **`animated-fiction.js` (`AnimatedFiction`)**: The main application class that integrates all the modules. It handles initialization, loading the story, and starting the application flow. - -## Supporting Libraries - -This engine relies on several external and internal libraries located in `public/js`: - -* `ink.js` (Loaded via CDN in `modular-index.html`, wrapped in `window.inkjs`) -* `smartypants.js` -* `linked-list.js` (Used by `linebreak.js`) -* `linebreak.js` (Core Knuth-Plass algorithm) -* `knuth-and-plass.js` (Adapter for `linebreak.js`) -* `Hyphenopoly_Loader.js` & `Hyphenopoly.js` (Hyphenation library) -* `kokoro-js.js`, `kokoro-handler.js`, `tts-handler.js`, `tts-factory.js` (Text-to-Speech components) - -## Usage - -1. **Include Libraries**: Ensure all necessary supporting libraries (`smartypants.js`, `ink.js`, `Hyphenopoly`, etc.) are loaded in your HTML file *before* the main application module. -2. **Include Main Module**: Load the main application module (`animated-fiction.js`) using ` - - - - - - - - - - - - - - - - - diff --git a/references/input-handler.js b/references/input-handler.js deleted file mode 100644 index 9584af3..0000000 --- a/references/input-handler.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Input Handler Module - * Manages the multi-line text input field with a custom cursor. - */ -export class InputHandler { - constructor(inputId = 'player_input', cursorId = 'cursor') { - this.playerInput = document.getElementById(inputId); - this.cursor = document.getElementById(cursorId); - this.commandInputContainer = document.getElementById('command_input'); // Assuming this container exists - - if (!this.playerInput || !this.cursor || !this.commandInputContainer) { - console.error('InputHandler: Required DOM elements not found.'); - return; - } - - this.commandSubmitCallback = null; // Callback for when a command is submitted - - this.bindEvents(); - this.adjustTextareaHeight(); // Initial adjustment - this.updateCursorPosition(); // Initial position - - // Setup handler for window load event to ensure proper initialization - window.addEventListener('load', () => { - console.log('InputHandler: Window loaded, adjusting text area height and cursor position'); - this.adjustTextareaHeight(); - this.updateCursorPosition(); - }); - } - - /** - * Register a callback function to be called when a command is submitted. - * @param {function(string)} callback - The function to call with the command text. - */ - onCommandSubmit(callback) { - this.commandSubmitCallback = callback; - } - - /** - * Bind event handlers to the input element. - */ - bindEvents() { - // Submit command on Enter key without Shift - this.playerInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); // Prevent default to avoid newline - this.submitCommand(); - } - // Allow Shift+Enter for new lines (default behavior) - }); - - // Auto-resize textarea and update cursor on input - this.playerInput.addEventListener('input', () => { - this.adjustTextareaHeight(); - this.updateCursorPosition(); - }); - - // Update cursor on various events - this.playerInput.addEventListener('click', this.updateCursorPosition.bind(this)); - this.playerInput.addEventListener('keyup', this.updateCursorPosition.bind(this)); - - // Show/hide cursor on focus/blur - this.playerInput.addEventListener('focus', () => { - if (this.cursor) this.cursor.style.opacity = '1'; - this.updateCursorPosition(); - }); - this.playerInput.addEventListener('blur', () => { - if (this.cursor) this.cursor.style.opacity = '0'; - }); - - // Handle paste events - this.playerInput.addEventListener('paste', () => { - // Use setTimeout to let the paste complete before adjusting - setTimeout(() => { - this.adjustTextareaHeight(); - this.updateCursorPosition(); - }, 10); - }); - - // Handle window resize - window.addEventListener('resize', () => { - this.adjustTextareaHeight(); - this.updateCursorPosition(); - }); - } - - /** - * Submit the current command. - */ - submitCommand() { - const command = this.playerInput.value.trim(); - if (command === '' || !this.commandSubmitCallback) return; - - // Fade out the input field container - if (this.commandInputContainer) { - this.commandInputContainer.classList.add('fading'); - } - - // Disable input temporarily - this.playerInput.disabled = true; - - // Call the registered callback - this.commandSubmitCallback(command); - - // Clear input - this.clearInput(); - } - - /** - * Clears the input field and resets its state. - */ - clearInput() { - this.playerInput.value = ''; - this.resetCursorPosition(); - this.adjustTextareaHeight(); - } - - /** - * Re-enables the input field after a command submission or response. - */ - enableInput() { - if (this.commandInputContainer) { - // Remove fading class and add fade-in animation - this.commandInputContainer.classList.remove('fading'); - this.commandInputContainer.classList.add('fade-in-input'); - - // Remove animation class after it completes - setTimeout(() => { - if (this.commandInputContainer) { - this.commandInputContainer.classList.remove('fade-in-input'); - } - }, 500); // Match CSS animation duration - } - - this.playerInput.disabled = false; - this.focus(); - } - - /** - * Focuses the input field. - */ - focus() { - this.playerInput.focus(); - // Ensure cursor is visible and positioned correctly after focus - setTimeout(() => { - if (this.cursor) this.cursor.style.opacity = '1'; - this.updateCursorPosition(); - }, 10); - } - - /** - * Gets the current value of the input field. - * @returns {string} The input text. - */ - getValue() { - return this.playerInput.value; - } - - /** - * Sets the value of the input field. - * @param {string} value - The text to set. - */ - setValue(value) { - this.playerInput.value = value; - this.adjustTextareaHeight(); - this.updateCursorPosition(); - this.focus(); // Focus after setting value - } - - /** - * Resets the cursor position to the start. - */ - resetCursorPosition() { - if (this.cursor) { - this.cursor.style.left = '0px'; - // Adjust top based on computed style padding or a default - const computedStyle = window.getComputedStyle(this.playerInput); - const paddingTop = parseFloat(computedStyle.paddingTop) || 6; - this.cursor.style.top = `${paddingTop}px`; - } - } - - /** - * Update the custom cursor position based on input text and caret position. - * Uses a temporary div for accurate measurement. - */ - updateCursorPosition() { - if (!this.cursor || !this.playerInput) return; - - const input = this.playerInput; - const cursor = this.cursor; - const caretPosition = input.selectionStart || 0; - const inputText = input.value; - - // If no text, position cursor at the beginning based on padding - if (inputText.length === 0 && caretPosition === 0) { - this.resetCursorPosition(); - return; - } - - // Create a temporary measurement div - const div = document.createElement('div'); - const style = getComputedStyle(input); - - // Apply relevant styles from the textarea to the div - div.style.position = 'absolute'; - div.style.top = '-9999px'; - div.style.left = '-9999px'; - div.style.width = style.width; - div.style.height = 'auto'; - div.style.padding = style.padding; - div.style.border = style.border; - div.style.fontFamily = style.fontFamily; - div.style.fontSize = style.fontSize; - div.style.fontWeight = style.fontWeight; - div.style.lineHeight = style.lineHeight; - div.style.whiteSpace = 'pre-wrap'; - div.style.wordWrap = 'break-word'; - div.style.boxSizing = style.boxSizing; - - // Create spans for text before and after the caret, and a marker span - const preCaretText = document.createTextNode(inputText.substring(0, caretPosition)); - const caretMarker = document.createElement('span'); - caretMarker.innerHTML = ' '; // Use non-breaking space for measurement - const postCaretText = document.createTextNode(inputText.substring(caretPosition)); - - // Append spans to the div - div.appendChild(preCaretText); - div.appendChild(caretMarker); - div.appendChild(postCaretText); - - // Append div to body for measurement - document.body.appendChild(div); - - // Get position relative to the div's content box - const markerRect = caretMarker.getBoundingClientRect(); - const divRect = div.getBoundingClientRect(); - - // Calculate position relative to the input's top-left, considering scroll - const cursorLeft = markerRect.left - divRect.left; - const cursorTop = markerRect.top - divRect.top - input.scrollTop; - - // Set cursor position - cursor.style.left = `${cursorLeft}px`; - cursor.style.top = `${cursorTop}px`; - - // Clean up the temporary div - document.body.removeChild(div); - } - - /** - * Adjust textarea height based on its content. - */ - adjustTextareaHeight() { - if (!this.playerInput) return; - const textarea = this.playerInput; - // Temporarily reset height to accurately measure scrollHeight - textarea.style.height = 'auto'; - // Set height to scrollHeight to fit content, adding a small buffer if needed - textarea.style.height = `${textarea.scrollHeight}px`; - } - - /** - * Sets up focus management to keep the input field focused. - * Note: Some parts might be better handled by the main application logic - * depending on overall focus requirements (e.g., clicking outside input). - */ - setupFocusManagement() { - // Focus input field when the handler is initialized - this.focus(); - - // Re-focus input when user returns to this browser tab/window - window.addEventListener('focus', () => this.focus()); - window.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - setTimeout(() => this.focus(), 100); - } - }); - - // Optional: Add a listener to the document to refocus if needed, - // but be careful not to interfere with other interactive elements. - /* - document.addEventListener('click', (e) => { - // Example: Refocus if click is not on specific elements - if (!e.target.closest('button, a, .interactive-ui-element')) { - this.focus(); - } - }); - */ - } -} diff --git a/references/localhost.log b/references/localhost.log deleted file mode 100644 index 4bad291..0000000 --- a/references/localhost.log +++ /dev/null @@ -1,210 +0,0 @@ -onpage-dialog.preload.js:121 Uncaught ReferenceError: browser is not defined - at start (onpage-dialog.preload.js:121:5) - at onpage-dialog.preload.js:135:1 - at onpage-dialog.preload.js:393:12 -start @ onpage-dialog.preload.js:121 -(anonymous) @ onpage-dialog.preload.js:135 -(anonymous) @ onpage-dialog.preload.js:393 -loader.js:12 Module registry initialized and assigned to window.moduleRegistry -loader.js:51 Module Loader: Initialization started -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: localization -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: text-processor -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: tts-factory -ui-input-handler.js:35 UIInputHandler: Constructor initialized -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: ui-display-handler -ui-input-handler.js:398 UIInputHandler: Registering with window -ui-effects.js:48 UIEffects: Constructor initialized -ui-effects.js:310 UIEffects: Registering with window -ui-display-handler.js:61 UIDisplayHandler: Constructor initialized -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: text-processor -ui-display-handler.js:581 UIDisplayHandler: Registering with window -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: text-processor -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: socket-client -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: text-processor -module-registry.js:111 Module Registry: Tracking potential circular dependency with unregistered module: tts -loader.js:168 Module dependencies: -loader.js:171 persistence-manager depends on: localization -loader.js:171 localization depends on: none -loader.js:171 paragraph-layout depends on: text-processor -loader.js:171 animation-queue depends on: none -loader.js:171 layout-renderer depends on: animation-queue -loader.js:171 audio-manager depends on: none -loader.js:171 text-buffer depends on: none -loader.js:171 tts-player depends on: tts-factory -loader.js:171 ui-input-handler depends on: ui-display-handler -loader.js:171 ui-effects depends on: none -loader.js:171 ui-display-handler depends on: paragraph-layout, layout-renderer, animation-queue -loader.js:171 ui-controller depends on: animation-queue, ui-display-handler, ui-input-handler, ui-effects, text-buffer, socket-client - socket-client depends on: text-buffer - options-ui depends on: persistence-manager, localization - game-loop depends on: ui-controller, socket-client, tts, text-buffer - text-processor depends on: localization - tts-factory depends on: persistence-manager, localization - Starting initialization of module: persistence-manager - Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (PENDING)', 'paragraph-layout (PENDING)', 'animation-queue (PENDING)', 'layout-renderer (PENDING)', 'audio-manager (PENDING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] - Starting initialization of module: localization - Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (PENDING)', 'animation-queue (PENDING)', 'layout-renderer (PENDING)', 'audio-manager (PENDING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] - Starting initialization of module: paragraph-layout - Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (PENDING)', 'layout-renderer (PENDING)', 'audio-manager (PENDING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: animation-queue -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (PENDING)', 'audio-manager (PENDING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: layout-renderer -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (PENDING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: audio-manager -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (PENDING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: text-buffer -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (PENDING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: tts-player -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (PENDING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: ui-input-handler -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (PENDING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: ui-effects -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (PENDING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: ui-display-handler -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (PENDING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: ui-controller -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (PENDING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: socket-client -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (PENDING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: options-ui -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (PENDING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: game-loop -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (PENDING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: text-processor -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (PENDING)'] -loader.js:197 Starting initialization of module: tts-factory -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (LOADING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'animation-queue (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'animation-queue (INITIALIZING)', 'layout-renderer (LOADING)', 'audio-manager (LOADING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'animation-queue (INITIALIZING)', 'layout-renderer (LOADING)', 'audio-manager (INITIALIZING)', 'text-buffer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'animation-queue (INITIALIZING)', 'layout-renderer (LOADING)', 'audio-manager (INITIALIZING)', 'text-buffer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (17) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'animation-queue (INITIALIZING)', 'layout-renderer (LOADING)', 'audio-manager (INITIALIZING)', 'text-buffer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (INITIALIZING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -ui-effects.js:79 UIEffects: Setting up effect elements -loader.js:354 Modules still pending: (16) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (LOADING)', 'audio-manager (INITIALIZING)', 'text-buffer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (INITIALIZING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (15) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (LOADING)', 'text-buffer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (INITIALIZING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (14) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-effects (INITIALIZING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (13) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:203 Completed initialization of module: animation-queue -loader.js:203 Completed initialization of module: audio-manager -loader.js:203 Completed initialization of module: text-buffer -loader.js:203 Completed initialization of module: ui-effects -loader.js:354 Modules still pending: (13) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (LOADING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (13) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'layout-renderer (INITIALIZING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (12) ['persistence-manager (LOADING)', 'localization (INITIALIZING)', 'paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:203 Completed initialization of module: layout-renderer -localization.js:102 - - - GET http://localhost:3001/locales/en-us.json 404 (Not Found) -loadTranslations @ localization.js:102 -initialize @ localization.js:61 -initializeInterface @ base-module.js:55 -await in initializeInterface -(anonymous) @ loader.js:200 -initializeModules @ loader.js:175 -(anonymous) @ loader.js:75 -Promise.then -init @ loader.js:73 -(anonymous) @ loader.js:586 -localization.js:111 - - - GET http://localhost:3001/locales/en.json 404 (Not Found) -loadTranslations @ localization.js:111 -await in loadTranslations -initialize @ localization.js:61 -initializeInterface @ base-module.js:55 -await in initializeInterface -(anonymous) @ loader.js:200 -initializeModules @ loader.js:175 -(anonymous) @ loader.js:75 -Promise.then -init @ loader.js:73 -(anonymous) @ loader.js:586 -localization.js:122 English translations not found, using empty set -loadTranslations @ localization.js:122 -await in loadTranslations -initialize @ localization.js:61 -initializeInterface @ base-module.js:55 -await in initializeInterface -(anonymous) @ loader.js:200 -initializeModules @ loader.js:175 -(anonymous) @ loader.js:75 -Promise.then -init @ loader.js:73 -(anonymous) @ loader.js:586 -localization.js:102 - - - GET http://localhost:3001/locales/de.json 404 (Not Found) -loadTranslations @ localization.js:102 -initialize @ localization.js:68 -await in initialize -initializeInterface @ base-module.js:55 -await in initializeInterface -(anonymous) @ loader.js:200 -initializeModules @ loader.js:175 -(anonymous) @ loader.js:75 -Promise.then -init @ loader.js:73 -(anonymous) @ loader.js:586 -loader.js:354 Modules still pending: (11) ['persistence-manager (LOADING)', 'paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:203 Completed initialization of module: localization -loader.js:354 Modules still pending: (11) ['persistence-manager (INITIALIZING)', 'paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (LOADING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (11) ['persistence-manager (INITIALIZING)', 'paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (10) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (LOADING)'] -loader.js:203 Completed initialization of module: persistence-manager -loader.js:354 Modules still pending: (10) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (INITIALIZING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (LOADING)'] -loader.js:354 Modules still pending: (10) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'options-ui (INITIALIZING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (INITIALIZING)'] -loader.js:354 Modules still pending: (9) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'socket-client (INITIALIZING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (INITIALIZING)'] -loader.js:203 Completed initialization of module: options-ui -socket-client.js:81 Socket Client: Using origin for connection: http://localhost:3001 -loader.js:354 Modules still pending: (8) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)', 'tts-factory (INITIALIZING)'] -loader.js:203 Completed initialization of module: socket-client -browser-tts-handler.js:227 Browser TTS: Loaded 23 voices from event -browser-tts-handler.js:269 Browser TTS: Using de voice: Microsoft Hedda - German (Germany) -tts-factory.js:201 TTS Factory: Successfully initialized browser TTS handler -text-processor.js:145 SmartyPants loaded successfully -text-processor.js:171 Initializing hyphenation with Hyphenopoly module -text-processor.js:185 Loading hyphenation pattern: /js/patterns/de.wasm -kokoro-handler.js:104 Kokoro worker is ready -kokoro-handler.js:202 Kokoro worker initialized successfully -kokoro-handler.js:753 Kokoro TTS: Set voice to German (Neural) -tts-factory.js:201 TTS Factory: Successfully initialized kokoro TTS handler -loader.js:354 Modules still pending: (7) ['paragraph-layout (LOADING)', 'tts-player (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)'] -loader.js:203 Completed initialization of module: tts-factory -loader.js:354 Modules still pending: (7) ['paragraph-layout (LOADING)', 'tts-player (INITIALIZING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)'] -loader.js:354 Modules still pending: (6) ['paragraph-layout (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)', 'text-processor (INITIALIZING)'] -loader.js:203 Completed initialization of module: tts-player -text-processor.js:208 Hyphenopoly engine ready for de -text-processor.js:218 Hyphenator ready for de -loader.js:354 Modules still pending: (5) ['paragraph-layout (LOADING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -loader.js:203 Completed initialization of module: text-processor -loader.js:354 Modules still pending: (5) ['paragraph-layout (INITIALIZING)', 'ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -loader.js:354 Modules still pending: (4) ['ui-input-handler (LOADING)', 'ui-display-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -loader.js:203 Completed initialization of module: paragraph-layout -loader.js:354 Modules still pending: (4) ['ui-input-handler (LOADING)', 'ui-display-handler (INITIALIZING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -ui-display-handler.js:162 UIDisplayHandler: CSS /css/style.css loaded successfully -ui-display-handler.js:212 UIDisplayHandler: Book container not found, creating it -ui-display-handler.js:221 UIDisplayHandler: Left page not found, creating it -ui-display-handler.js:285 UIDisplayHandler: Right page not found, creating it -ui-display-handler.js:294 UIDisplayHandler: Story container not found, creating it -ui-display-handler.js:304 UIDisplayHandler: Paragraphs container not found, creating it -ui-display-handler.js:326 UIDisplayHandler: All containers initialized -loader.js:354 Modules still pending: (3) ['ui-input-handler (LOADING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -loader.js:203 Completed initialization of module: ui-display-handler -loader.js:354 Modules still pending: (3) ['ui-input-handler (INITIALIZING)', 'ui-controller (LOADING)', 'game-loop (LOADING)'] -ui-input-handler.js:83 UIInputHandler: Setting up input elements in document flow -ui-input-handler.js:182 UIInputHandler: Input elements setup complete -loader.js:354 Modules still pending: (2) ['ui-controller (LOADING)', 'game-loop (LOADING)'] -loader.js:203 Completed initialization of module: ui-input-handler -loader.js:354 Modules still pending: (2) ['ui-controller (INITIALIZING)', 'game-loop (LOADING)']0: "ui-controller (INITIALIZING)"1: "game-loop (LOADING)"length: 2[[Prototype]]: Array(0) -ui-controller.js:271 UIController: Setting up text buffer callback -text-buffer.js:68 Text Buffer: Sentence ready callback set -ui-controller.js:292 UIController: Text buffer callback set up -loader.js:354 Modules still pending: ['game-loop (LOADING)']0: "game-loop (LOADING)"length: 1[[Prototype]]: Array(0) -loader.js:203 Completed initialization of module: ui-controller -loader.js:354 Modules still pending: ['game-loop (WAITING)']0: "game-loop (WAITING)"length: 1[[Prototype]]: Array(0) -loader.js:203 Completed initialization of module: game-loop -animation-queue.js:52 Animation Queue: TTS module not found yet, will try again when needed -layout-renderer.js:55 Layout Renderer: TTS Player module not found yet, will try again when needed diff --git a/references/ui-controller.js b/references/ui-controller.js deleted file mode 100644 index 5b2d90d..0000000 --- a/references/ui-controller.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * UiController Module - * Manages user interface interactions and updates UI elements. - */ -export class UiController { - /** - * Create a new UiController - * @param {Object} config - Configuration options - * @param {Object} config.animationQueue - The AnimationQueue instance - * @param {Object} config.ttsPlayer - The TtsPlayer instance - * @param {Object} config.inputHandler - The InputHandler instance - * @param {Object} config.socketClient - The SocketClient instance (or rely on callbacks) - * @param {HTMLElement} config.commandHistoryContainerElement - The command history container - * @param {HTMLElement} config.storyContainerElement - The story container - * @param {HTMLElement} config.speedSliderElement - The speed slider element - * @param {HTMLElement} config.rewindButtonElement - The rewind button element - * @param {HTMLElement} config.saveButtonElement - The save button element - * @param {HTMLElement} config.loadButtonElement - The load button element - * @param {HTMLElement} config.speechButtonElement - The speech button element - * @param {HTMLElement} config.speedResetElement - The speed reset button element - * @param {Object} config.translations - Translations object - * @param {string} config.locale - Locale string - */ - constructor(config = {}) { - // Store dependencies - this.animationQueue = config.animationQueue; - this.ttsPlayer = config.ttsPlayer; // Handles enabling/disabling TTS via its own logic - this.inputHandler = config.inputHandler; // Needed for focus, suggestions? - this.socketClient = config.socketClient; // Direct access or use callbacks - - // Callbacks for actions (to be set by AnimatedFiction) - this.onRestartRequest = null; - this.onSaveRequest = null; - this.onLoadRequest = null; - - // Active TTS handler (set via setTtsHandler) - this.ttsHandler = null; - - // UI elements - this.speedSlider = config.speedSliderElement || document.getElementById('speed'); - this.commandHistoryContainer = config.commandHistoryContainerElement; // Added - this.storyContainer = config.storyContainerElement; // Added - this.rewindButton = config.rewindButtonElement || document.getElementById('rewind'); - this.saveButton = config.saveButtonElement || document.getElementById('save'); - this.loadButton = config.loadButtonElement || document.getElementById('reload'); - this.speechButton = config.speechButtonElement || document.getElementById('speech'); - this.speedReset = config.speedResetElement || document.getElementById('speed_reset'); - - // Translations - this.translations = config.translations || {}; - this.locale = config.locale || 'en-us'; - - // Initial UI state - this.updateButtonStates({ started: false, canLoad: false }); // Start with buttons disabled - this.updateSpeechButtonAvailability(false); // Start with speech disabled - } - - /** - * Set up event listeners - */ - setupEventListeners() { - // Speed slider - if (this.speedSlider) { - this.speedSlider.addEventListener('input', this.handleSpeedChange.bind(this)); - } - - // Speed reset button - if (this.speedReset) { - this.speedReset.addEventListener('click', this.handleSpeedReset.bind(this)); - } - - // Rewind button - if (this.rewindButton) { - this.rewindButton.addEventListener('click', this.handleRewindClick.bind(this)); - } - - // Save button - if (this.saveButton) { - this.saveButton.addEventListener('click', this.handleSaveClick.bind(this)); - } - - // Load button - if (this.loadButton) { - this.loadButton.addEventListener('click', this.handleLoadClick.bind(this)); - } - - // Speech button - if (this.speechButton) { - this.speechButton.addEventListener('click', this.handleSpeechToggle.bind(this)); - } - - // Fast forward (spacebar or click on right page) - window.addEventListener('keydown', (event) => { - if (event.code === 'Space') { - this.handleFastForward(); - } - }); - - document.getElementById('page_right')?.addEventListener('click', this.handleFastForward.bind(this)); - - // Window resize - window.addEventListener('resize', this.handleWindowResize.bind(this)); - } - - /** - * Handle speed slider change - * @param {Event} event - The input event - */ - handleSpeedChange(event) { - if (!this.animationQueue) return; - - const value = parseFloat(event.target.value); - const speed = Math.pow(100.0 - value, 3) / 10000 * 10 + 0.01; - this.animationQueue.setSpeed(speed); - } - - /** - * Handle speed reset button click - */ - handleSpeedReset() { - if (!this.speedSlider || !this.animationQueue) return; - - this.speedSlider.value = 50; - const speed = Math.pow(100.0 - 50, 3) / 10000 * 10 + 0.01; - this.animationQueue.setSpeed(speed); - } - - /** - * Handle rewind button click - */ - handleRewindClick() { - if (this.rewindButton.getAttribute('disabled') === 'disabled') { - return; - } - // Use localized confirm message if available - const confirmMsg = this.translations[this.locale]?.confirm_restart || 'Are you sure you want to restart the game? All progress will be lost.'; - if (confirm(confirmMsg)) { - if (this.onRestartRequest) { - this.onRestartRequest(); - } else { - console.warn("UiController: onRestartRequest callback not set."); - } - } - } - - /** - * Handle save button click - */ - handleSaveClick() { - if (this.saveButton.getAttribute('disabled') === 'disabled') { - return; - } - if (this.onSaveRequest) { - this.onSaveRequest(); - } else { - console.warn("UiController: onSaveRequest callback not set."); - } - } - - /** - * Handle load button click - */ - handleLoadClick() { - if (this.loadButton.getAttribute('disabled') === 'disabled') { - return; - } - if (this.onLoadRequest) { - this.onLoadRequest(); - } else { - console.warn("UiController: onLoadRequest callback not set."); - } - } - - /** - * Handle speech toggle button click - */ - handleSpeechToggle() { - if (!this.ttsHandler) { - console.warn("UiController: ttsHandler not set. Cannot toggle speech."); - // Attempt to use ttsPlayer as fallback if needed, but prefer ttsHandler - if (this.ttsPlayer && this.speechButton.getAttribute('disabled') !== 'disabled') { - const enabled = this.ttsPlayer.toggle(); - this.updateSpeechButtonStyling(enabled); - } - return; - } - - if (this.speechButton.getAttribute('disabled') === 'disabled') { - return; - } - - // Ensure AudioContext is resumed on user interaction if using Kokoro - if (window.ttsFactory && window.ttsFactory.usingKokoro && this.ttsHandler.audioContext && this.ttsHandler.audioContext.state === 'suspended') { - this.ttsHandler.audioContext.resume().catch(err => console.error('Error resuming AudioContext on click:', err)); - } - - // Set user activation flag for the handler - this.ttsHandler.hasUserActivation = true; - const enabled = this.ttsHandler.toggle(); - this.updateSpeechButtonStyling(enabled); // Update visual style - - if (enabled) { - // Speak the last narrative if speech was just enabled and story container is available - if (this.storyContainer) { - const lastNarrative = this.storyContainer.lastElementChild; - if (lastNarrative && lastNarrative.classList.contains('narrative')) { // Check if it's narrative text - console.log("Speaking last narrative on toggle"); - // Use a slight delay to ensure audio context is resumed - setTimeout(() => this.ttsHandler.speak(lastNarrative.textContent), 50); - } - } - } else { - // If disabling, ensure speech stops - this.ttsHandler.stop(); - } - } - - /** - * Handle fast forward (spacebar or click) - */ - handleFastForward() { - if (!this.animationQueue) return; - - this.animationQueue.fastForward(); - } - - /** - * Handle window resize - */ - handleWindowResize() { - this.updateBookDimensions(); - this.updateParagraphHeight(); - } - - /** - * Sets the active TTS handler. - * @param {object} handler - The TTS handler instance (e.g., KokoroHandler, BrowserTtsHandler). - */ - setTtsHandler(handler) { - this.ttsHandler = handler; - console.log("UiController: TTS Handler set.", handler); - // Update button state based on the new handler's status - this.updateSpeechButtonStyling(this.ttsHandler ? this.ttsHandler.isEnabled() : false); - } - - /** - * Update the book dimensions based on viewport size - */ - updateBookDimensions() { - const vw = window.innerWidth; - const vh = window.innerHeight; - const viewportAspectRatio = vw / vh; - const imageAspectRatio = 2727 / 1691; - - let bookWidth, bookHeight; - - if (viewportAspectRatio > imageAspectRatio) { - bookWidth = vh * imageAspectRatio; - bookHeight = vh; - } else { - bookWidth = vw; - bookHeight = vw / imageAspectRatio; - } - - document.documentElement.style.setProperty('--book-width', `${bookWidth}px`); - document.documentElement.style.setProperty('--book-height', `${bookHeight}px`); - - // Setting a CSS variable that will be either vw or vh depending on the viewport aspect ratio - document.documentElement.style.setProperty( - "--viewport-dimension", - viewportAspectRatio > imageAspectRatio ? 'vw' : 'vh' - ); - - document.documentElement.style.setProperty('--viewport-aspect-ratio', viewportAspectRatio); - - const story = document.getElementById("story"); - if (story) { - const paddingTop = window.getComputedStyle(story).paddingTop; - const paddingBottom = window.getComputedStyle(story).paddingBottom; - document.documentElement.style.setProperty('--story-line-height', (story.clientHeight - paddingTop - paddingBottom) / 28); - } - } - - /** - * Update paragraph heights based on viewport - */ - updateParagraphHeight() { - document.querySelectorAll("#story p").forEach((element) => { - if (element.dataset.vpc) { - const pHeight = parseFloat(window.getComputedStyle(document.getElementById('page_right')).height); - const newHeight = pHeight * element.dataset.vpc / 100 + 'px'; - element.style.height = newHeight; - } - }); - } - - /** - * Update the speech button styling based on enabled state. - * @param {boolean} enabled - Whether speech is enabled. - */ - updateSpeechButtonStyling(enabled = false) { - if (!this.speechButton) return; - - if (enabled) { - this.speechButton.style.fontWeight = 'bold'; - this.speechButton.style.color = '#000'; - this.speechButton.style.backgroundColor = '#eee'; - } else { - this.speechButton.style.fontWeight = 'normal'; - this.speechButton.style.color = '#333'; - this.speechButton.style.backgroundColor = ''; - } - } - - /** - * Updates the enabled/disabled state and title of the speech button. - * @param {boolean} available - Whether any TTS system is available. - * @param {string} [type] - The type of TTS system available ('kokoro', 'browser', etc.). - */ - updateSpeechButtonAvailability(available, type) { - if (!this.speechButton) return; - - if (available) { - this.speechButton.removeAttribute('disabled'); - const ttsName = type === 'kokoro' ? 'Kokoro TTS' : (type === 'browser' ? 'Browser TTS' : 'TTS'); - const title = this.translations[this.locale]?.title_speech || `Toggle Text-to-Speech (${ttsName})`; - this.speechButton.setAttribute('title', title); - // Update style based on current handler state if available - this.updateSpeechButtonStyling(this.ttsHandler ? this.ttsHandler.isEnabled() : false); - } else { - this.speechButton.setAttribute('disabled', 'disabled'); - const title = this.translations[this.locale]?.title_speech_unavailable || 'Text-to-Speech not available'; - this.speechButton.setAttribute('title', title); - this.updateSpeechButtonStyling(false); // Ensure style is off - } - } - - /** - * Updates the enabled/disabled state of control buttons based on game state. - * @param {object} gameState - The current game state from AnimatedFiction. - * @param {boolean} gameState.started - Whether the game has started. - * @param {boolean} [gameState.canLoad] - Whether a saved game exists to be loaded. - */ - updateButtonStates(gameState) { - if (this.rewindButton) { - if (gameState.started) { - this.rewindButton.removeAttribute('disabled'); - } else { - this.rewindButton.setAttribute('disabled', 'disabled'); - } - } - if (this.saveButton) { - if (gameState.started) { - this.saveButton.removeAttribute('disabled'); - } else { - this.saveButton.setAttribute('disabled', 'disabled'); - } - } - if (this.loadButton) { - // Enable load button if a save exists (indicated by canLoad flag or similar) - // We might need a more robust way to check for saved state existence. - // For now, enable if game started OR if canLoad is explicitly true. - if (gameState.started || gameState.canLoad) { - this.loadButton.removeAttribute('disabled'); - } else { - this.loadButton.setAttribute('disabled', 'disabled'); - } - } - // Speech button availability is handled separately by updateSpeechButtonAvailability - } - - /** - * Updates the visual display of the speed slider. - * @param {number} value - The speed value (0-100). - */ - updateSpeedDisplay(value) { - if (this.speedSlider) { - this.speedSlider.value = value; - } - } - - /** - * Insert an element after a delay (Helper, potentially move elsewhere or keep if used) - * @param {number} delay - The delay in milliseconds - * @param {HTMLElement} target - The target element to append to - * @param {HTMLElement} el - The element to insert - * @param {boolean} fadeIn - Whether to fade in the element - */ - insertAfter(delay, target, el, fadeIn = true) { - if (this.animationQueue) { - if (fadeIn) { - el.classList.add("fade-in"); - this.animationQueue.schedule(function() { - target.appendChild(el); - }, delay); - } else { - this.animationQueue.schedule(function() { - target.appendChild(el); - }, delay); - } - } else { - // Fallback if no animation queue - if (fadeIn) { - el.classList.add("fade-in"); - setTimeout(() => { - target.appendChild(el); - }, delay); - } else { - setTimeout(() => { - target.appendChild(el); - }, delay); - } - } - } - - /** - * Set the locale for translations - * @param {string} locale - The locale code - */ - setLocale(locale) { - this.locale = locale; - - if (this.translations[locale]) { - Object.keys(this.translations[locale]).forEach(key => { - const prefix = key.substring(0, 5); - const postfix = key.substring(6, key.length); - const elements = document.querySelectorAll(`.l10n-${(prefix === 'title' ? postfix : key)}`); - - elements.forEach(element => { - if (prefix === "title") { - element.title = this.translations[locale][key]; - } else { - element.innerHTML = this.translations[locale][key]; - } - }); - }); - } else { - console.error(`Locale ${locale} is not defined`); - } - } -}