Updated options ui styles, and browser tts voice selection.

This commit is contained in:
2025-04-05 12:40:16 +00:00
parent 5cd9adcb46
commit b8e2e6e238
2 changed files with 155 additions and 43 deletions
+99 -41
View File
@@ -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;
}
+56 -2
View File
@@ -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