From b8e2e6e23866928a786c90334b2102e98a3f12c8 Mon Sep 17 00:00:00 2001 From: Georg Tomitsch Date: Sat, 5 Apr 2025 12:40:16 +0000 Subject: [PATCH] Updated options ui styles, and browser tts voice selection. --- public/css/style.css | 140 ++++++++++++++++++++++--------- public/js/browser-tts-handler.js | 58 ++++++++++++- 2 files changed, 155 insertions(+), 43 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 64df7a3..70692fb 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -737,89 +737,145 @@ ol.choice { } .options-content { - background-color: rgba(255, 255, 255, 0.95); - border-radius: 5px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); - width: 80%; - max-width: 500px; - max-height: 90vh; + background-color: rgba(255, 252, 245, 0.97); + border-radius: 0.4rem; + box-shadow: 0 0 1.5rem rgba(0, 0, 0, 0.4); + width: calc(var(--book-height) * 0.7); + max-width: 90%; + max-height: calc(var(--book-height) * 0.9); overflow-y: auto; - padding: 20px; - font-family: var(--book-font); + padding: 1.5rem; + font-family: 'EB Garamond', var(--book-font), serif; color: #333; position: relative; + border: 1px solid #d9c8a9; + font-size: 1rem; + /* Scale like the book */ + transform-origin: center center; } .options-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 20px; - border-bottom: 1px solid #ccc; - padding-bottom: 10px; + margin-bottom: 1.2rem; + border-bottom: 1px solid #d9c8a9; + padding-bottom: 0.7rem; } .options-header h2 { margin: 0; - font-family: var(--book-font); + font-family: 'EB Garamond', var(--book-font), serif; font-weight: normal; - font-size: 1.5rem; + font-size: 1.4rem; + color: #5a3921; + letter-spacing: 0.02rem; } .options-close { background: none; border: none; - font-size: 1.5rem; + font-size: 1.4rem; cursor: pointer; - color: #666; + color: #7a6e59; + font-family: 'EB Garamond', var(--book-font), serif; } .options-close:hover { - color: #000; + color: #5a3921; } .options-section { - margin-bottom: 20px; + margin-bottom: 1.5rem; } .options-section h3 { - font-family: var(--book-font); + font-family: 'EB Garamond', var(--book-font), serif; font-weight: normal; font-size: 1.2rem; margin-top: 0; - margin-bottom: 10px; - border-bottom: 1px solid #eee; - padding-bottom: 5px; + margin-bottom: 0.8rem; + border-bottom: 1px solid #e9ddc8; + padding-bottom: 0.4rem; + color: #5a3921; } .options-row { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 10px; - padding: 5px 0; + margin-bottom: 0.7rem; + padding: 0.25rem 0; } .options-row label { - flex: 1; + font-family: 'EB Garamond', var(--book-font), serif; + font-size: 1rem; + color: #4a4234; } -.options-row select, -.options-row input[type="range"] { - flex: 1; +/* Elegant checkboxes */ +.options-row input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + width: 1rem; + height: 1rem; + border: 1px solid black; + border-radius: 0.25rem; + outline: none; + cursor: pointer; + position: relative; + background-color: transparent; + overflow: hidden; } -/* Style range inputs to match the top-left menu */ +.options-row input[type="checkbox"]:checked::before { + content: "✓"; + position: absolute; + font-family: 'EB Garamond', var(--book-font), serif; + font-size: 0.9rem; + top: -0.05rem; + left: 0.15rem; + color: rgba(0,0,0,0.9); +} + +/* Elegant select dropdowns */ +.options-row select { + appearance: none; + -webkit-appearance: none; + background-color: transparent; + border: 1px solid black; + border-radius: 0.25rem; + padding: 0.3rem 1.5rem 0.3rem 0.5rem; + font-family: 'EB Garamond', var(--book-font), serif; + font-size: 1rem; + color: rgba(0,0,0,0.9); + cursor: pointer; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 9l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 0.4rem center; + background-size: 0.6rem; + min-width: 8rem; +} + +.options-row select:focus { + outline: none; +} + +/* Range inputs (sliders) - match the main menu style */ .options-row input[type="range"] { -webkit-appearance: none; appearance: none; - width: 100%; + width: 8rem; height: 0.5rem; background-color: transparent; box-sizing: border-box; border: 1px solid black; border-radius: 0.25rem; + outline: none; + margin: 0; overflow: hidden; + cursor: pointer; } .options-row input[type="range"]::-webkit-slider-thumb { @@ -833,23 +889,25 @@ ol.choice { box-shadow: -407px 0 0 400px rgba(0,0,0,0.3); } -.options-row input[type="range"]::-webkit-slider-runnable-track { - -webkit-appearance: none; - appearance: none; +.options-row input[type="range"]::-moz-range-thumb { height: 0.5rem; + width: 0.5rem; border-radius: 0.25rem; + background-color: rgba(0,0,0,0.9); + border: none; + box-shadow: -407px 0 0 400px rgba(0,0,0,0.3); } -/* Reload notice styled like the book theme */ +/* Reload notice */ .reload-notice { - margin-top: 20px; - padding: 10px; + margin-top: 1rem; + padding: 0.5rem; + border: 1px solid #d9c8a9; + border-radius: 0.25rem; + background-color: rgba(233, 221, 200, 0.3); + font-family: 'EB Garamond', var(--book-font), serif; font-style: italic; - color: #666; + font-size: 0.9rem; + color: #7a6e59; text-align: center; } - -.reload-notice span { - font-family: var(--book-font); - font-size: 0.9rem; -} diff --git a/public/js/browser-tts-handler.js b/public/js/browser-tts-handler.js index fd55bcc..a76399c 100644 --- a/public/js/browser-tts-handler.js +++ b/public/js/browser-tts-handler.js @@ -547,13 +547,67 @@ export class BrowserTTSHandler extends TTSHandler { * @returns {Array} - Array of voice objects */ getVoices() { - return this.voices.map(voice => ({ + // Get localization module for current locale + const localization = this.getModule('localization'); + let currentLocale = localization ? localization.getLocale().toLowerCase() : 'en-us'; + + // Create language code variations for matching + const languageCode = currentLocale.split('-')[0]; // e.g., 'en' from 'en-us' + + // Filter voices by current locale + const filteredVoices = this.voices.filter(voice => { + const voiceLang = voice.lang.toLowerCase(); + return voiceLang.startsWith(languageCode) || + voiceLang === currentLocale || + // For handling cases like 'en' matching 'en-us' + (currentLocale.startsWith(voiceLang) && voiceLang.length === 2); + }); + + // If no matching voices found, fall back to all voices + const voicesToUse = filteredVoices.length > 0 ? filteredVoices : this.voices; + + return voicesToUse.map(voice => ({ id: voice.voiceURI, name: voice.name, - language: voice.lang + lang: voice.lang, + // Add proper gender field if available, otherwise infer from name + gender: this.inferVoiceGender(voice.name) })); } + /** + * Infer voice gender from name + * @param {string} name - Voice name + * @returns {string} - Inferred gender ('male', 'female', or 'unknown') + */ + inferVoiceGender(name) { + const lowerName = name.toLowerCase(); + + // Common terms indicating gender + const maleTerms = ['male', 'man', 'guy', 'boy', 'mr', 'sir', 'him', 'his']; + const femaleTerms = ['female', 'woman', 'lady', 'girl', 'ms', 'mrs', 'miss', 'her', 'hers']; + + // Check for explicit gender terms in the name + for (const term of maleTerms) { + if (lowerName.includes(term)) return 'male'; + } + + for (const term of femaleTerms) { + if (lowerName.includes(term)) return 'female'; + } + + // Common male/female voice names + if (/(david|james|john|paul|mark|thomas|daniel|jack|william|george|michael|robert|peter|brian|richard|steve|bruce)/i.test(lowerName)) { + return 'male'; + } + + if (/(mary|sarah|emma|susan|julia|karen|lisa|anna|laura|amy|elizabeth|jennifer|maria|emily|jessica|alice|victoria)/i.test(lowerName)) { + return 'female'; + } + + return 'unknown'; + } + /** * Set voice options * @param {Object} options - Voice options