Updated options ui styles, and browser tts voice selection.
This commit is contained in:
+99
-41
@@ -737,89 +737,145 @@ ol.choice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.options-content {
|
.options-content {
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 252, 245, 0.97);
|
||||||
border-radius: 5px;
|
border-radius: 0.4rem;
|
||||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 1.5rem rgba(0, 0, 0, 0.4);
|
||||||
width: 80%;
|
width: calc(var(--book-height) * 0.7);
|
||||||
max-width: 500px;
|
max-width: 90%;
|
||||||
max-height: 90vh;
|
max-height: calc(var(--book-height) * 0.9);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 20px;
|
padding: 1.5rem;
|
||||||
font-family: var(--book-font);
|
font-family: 'EB Garamond', var(--book-font), serif;
|
||||||
color: #333;
|
color: #333;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border: 1px solid #d9c8a9;
|
||||||
|
font-size: 1rem;
|
||||||
|
/* Scale like the book */
|
||||||
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-header {
|
.options-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 1.2rem;
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #d9c8a9;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-header h2 {
|
.options-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: var(--book-font);
|
font-family: 'EB Garamond', var(--book-font), serif;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 1.5rem;
|
font-size: 1.4rem;
|
||||||
|
color: #5a3921;
|
||||||
|
letter-spacing: 0.02rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-close {
|
.options-close {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 1.5rem;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #666;
|
color: #7a6e59;
|
||||||
|
font-family: 'EB Garamond', var(--book-font), serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-close:hover {
|
.options-close:hover {
|
||||||
color: #000;
|
color: #5a3921;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-section {
|
.options-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-section h3 {
|
.options-section h3 {
|
||||||
font-family: var(--book-font);
|
font-family: 'EB Garamond', var(--book-font), serif;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 0.8rem;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #e9ddc8;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 0.4rem;
|
||||||
|
color: #5a3921;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-row {
|
.options-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 0.7rem;
|
||||||
padding: 5px 0;
|
padding: 0.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-row label {
|
.options-row label {
|
||||||
flex: 1;
|
font-family: 'EB Garamond', var(--book-font), serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #4a4234;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-row select,
|
/* Elegant checkboxes */
|
||||||
.options-row input[type="range"] {
|
.options-row input[type="checkbox"] {
|
||||||
flex: 1;
|
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"] {
|
.options-row input[type="range"] {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 100%;
|
width: 8rem;
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
outline: none;
|
||||||
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-row input[type="range"]::-webkit-slider-thumb {
|
.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);
|
box-shadow: -407px 0 0 400px rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-row input[type="range"]::-webkit-slider-runnable-track {
|
.options-row input[type="range"]::-moz-range-thumb {
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
|
width: 0.5rem;
|
||||||
border-radius: 0.25rem;
|
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 {
|
.reload-notice {
|
||||||
margin-top: 20px;
|
margin-top: 1rem;
|
||||||
padding: 10px;
|
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;
|
font-style: italic;
|
||||||
color: #666;
|
font-size: 0.9rem;
|
||||||
|
color: #7a6e59;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reload-notice span {
|
|
||||||
font-family: var(--book-font);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -547,13 +547,67 @@ export class BrowserTTSHandler extends TTSHandler {
|
|||||||
* @returns {Array} - Array of voice objects
|
* @returns {Array} - Array of voice objects
|
||||||
*/
|
*/
|
||||||
getVoices() {
|
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,
|
id: voice.voiceURI,
|
||||||
name: voice.name,
|
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
|
* Set voice options
|
||||||
* @param {Object} options - Voice options
|
* @param {Object} options - Voice options
|
||||||
|
|||||||
Reference in New Issue
Block a user