feat(bisaya-course): enhance German course content and localization support
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
- Updated the create-german-for-bisaya-course-content.js script to improve lesson pattern retrieval by introducing a new function for generating a lesson pattern pool. - Added new exercises for various topics including 'Wohnung & Nachbarn', 'Besuch empfangen', 'Arzt, Apotheke, Termin', and 'Amt, Dokumente, Anmeldung', enhancing practical language skills for learners. - Improved localization by integrating translation keys for various UI elements and error messages across multiple components, ensuring a consistent user experience in both German and Bisaya. - Enhanced the main.js file to recognize Bisaya language preferences in browser settings, improving accessibility for users.
This commit is contained in:
@@ -1,61 +1,61 @@
|
||||
<template>
|
||||
<div class="blog-editor">
|
||||
<h1>{{ isEdit ? 'Blog bearbeiten' : 'Blog erstellen' }}</h1>
|
||||
<h1>{{ isEdit ? $t('blog.editor.editTitle') : $t('blog.editor.createTitle') }}</h1>
|
||||
<form @submit.prevent="save">
|
||||
<div>
|
||||
<label>Titel</label>
|
||||
<label>{{ $t('blog.title') }}</label>
|
||||
<input v-model="form.title" required />
|
||||
</div>
|
||||
<div>
|
||||
<label>Beschreibung</label>
|
||||
<label>{{ $t('blog.editor.description') }}</label>
|
||||
<textarea v-model="form.description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label>Sichtbarkeit</label>
|
||||
<label>{{ $t('blog.editor.visibility') }}</label>
|
||||
<select v-model="form.visibility">
|
||||
<option value="public">Öffentlich</option>
|
||||
<option value="logged_in">Nur eingeloggte Nutzer</option>
|
||||
<option value="public">{{ $t('blog.editor.visibilityPublic') }}</option>
|
||||
<option value="logged_in">{{ $t('blog.editor.visibilityLoggedIn') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="form.visibility === 'logged_in'">
|
||||
<label>Altersbereich</label>
|
||||
<label>{{ $t('blog.editor.ageRange') }}</label>
|
||||
<div class="row">
|
||||
<input type="number" min="0" v-model.number="form.ageMin" placeholder="min" />
|
||||
<input type="number" min="0" v-model.number="form.ageMax" placeholder="max" />
|
||||
</div>
|
||||
<label>Geschlecht</label>
|
||||
<label>{{ $t('blog.editor.gender') }}</label>
|
||||
<div class="row">
|
||||
<label><input type="checkbox" value="m" v-model="genderSel"> Männlich</label>
|
||||
<label><input type="checkbox" value="f" v-model="genderSel"> Weiblich</label>
|
||||
<label><input type="checkbox" value="m" v-model="genderSel"> {{ $t('blog.editor.genderMale') }}</label>
|
||||
<label><input type="checkbox" value="f" v-model="genderSel"> {{ $t('blog.editor.genderFemale') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" type="submit">Speichern</button>
|
||||
<button class="btn" type="submit">{{ $t('blog.editor.save') }}</button>
|
||||
</form>
|
||||
|
||||
<div v-if="isEdit" class="post-editor">
|
||||
<h2>Neuer Beitrag</h2>
|
||||
<h2>{{ $t('blog.editor.newPostTitle') }}</h2>
|
||||
<form @submit.prevent="addPost">
|
||||
<input v-model="post.title" placeholder="Titel" required />
|
||||
<input v-model="post.title" :placeholder="$t('blog.title')" required />
|
||||
<RichTextEditor v-model="post.content" :blog-id="$route.params.id" />
|
||||
<button class="btn" type="submit">Beitrag hinzufügen</button>
|
||||
<button class="btn" type="submit">{{ $t('blog.editor.addPost') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="isEdit" class="share-section">
|
||||
<h2>Blog teilen</h2>
|
||||
<h2>{{ $t('blog.editor.shareTitle') }}</h2>
|
||||
<div class="share-url">
|
||||
<label>URL</label>
|
||||
<label>{{ $t('blog.editor.url') }}</label>
|
||||
<input :value="currentShareUrl" readonly @focus="$event.target.select()" />
|
||||
<button class="btn" type="button" @click="copyUrl">Link kopieren</button>
|
||||
<button class="btn" type="button" @click="copyUrl">{{ $t('blog.editor.copyLink') }}</button>
|
||||
</div>
|
||||
<div class="share-actions">
|
||||
<button class="btn" type="button" @click="shareToFriends">An Freunde senden</button>
|
||||
<button class="btn" type="button" @click="shareToFriends">{{ $t('blog.editor.shareToFriends') }}</button>
|
||||
</div>
|
||||
<div class="share-email">
|
||||
<label>E-Mail-Adressen (Kommagetrennt)</label>
|
||||
<label>{{ $t('blog.editor.emailAddresses') }}</label>
|
||||
<input v-model="emailInput" placeholder="name@example.com, second@example.org" />
|
||||
<button class="btn" type="button" @click="shareToEmails">Senden</button>
|
||||
<p v-if="form.visibility !== 'public'" class="hint">Hinweis: Dieser Blog ist nicht öffentlich. Empfänger benötigen ggf. ein Login und passende Alters/Geschlechts-Berechtigung.</p>
|
||||
<button class="btn" type="button" @click="shareToEmails">{{ $t('blog.editor.send') }}</button>
|
||||
<p v-if="form.visibility !== 'public'" class="hint">{{ $t('blog.editor.restrictedHint') }}</p>
|
||||
</div>
|
||||
<p v-if="shareStatus" class="status">{{ shareStatus }}</p>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
async save() {
|
||||
if (this.form.visibility === 'logged_in') {
|
||||
if (this.form.ageMin != null && this.form.ageMax != null && this.form.ageMin > this.form.ageMax) {
|
||||
showError(this, 'Ungültiger Altersbereich');
|
||||
showError(this, 'tr:blog.editor.invalidAgeRange');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -152,9 +152,9 @@ export default {
|
||||
const url = this.currentShareUrl || this.blogAbsoluteUrl();
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
this.shareStatus = 'Link kopiert';
|
||||
this.shareStatus = this.$t('blog.editor.copySuccess');
|
||||
} catch {
|
||||
this.shareStatus = 'Kopieren fehlgeschlagen';
|
||||
this.shareStatus = this.$t('blog.editor.copyError');
|
||||
}
|
||||
setTimeout(() => (this.shareStatus = ''), 2000);
|
||||
},
|
||||
@@ -162,9 +162,9 @@ export default {
|
||||
try {
|
||||
const res = await shareBlog(this.$route.params.id, { toFriends: true });
|
||||
if (res.url) this.currentShareUrl = res.url;
|
||||
this.shareStatus = `An ${res.notifiedFriends || 0} Freund(e) gesendet.`;
|
||||
this.shareStatus = this.$t('blog.editor.friendsSent', { count: res.notifiedFriends || 0 });
|
||||
} catch (e) {
|
||||
this.shareStatus = 'Teilen fehlgeschlagen';
|
||||
this.shareStatus = this.$t('blog.editor.shareError');
|
||||
}
|
||||
setTimeout(() => (this.shareStatus = ''), 3000);
|
||||
},
|
||||
@@ -174,9 +174,9 @@ export default {
|
||||
try {
|
||||
const res = await shareBlog(this.$route.params.id, { emails });
|
||||
if (res.url) this.currentShareUrl = res.url;
|
||||
this.shareStatus = `${res.emailsSent || 0} E-Mail(s) versendet.`;
|
||||
this.shareStatus = this.$t('blog.editor.emailsSent', { count: res.emailsSent || 0 });
|
||||
} catch (e) {
|
||||
this.shareStatus = 'E-Mail-Versand fehlgeschlagen';
|
||||
this.shareStatus = this.$t('blog.editor.emailError');
|
||||
}
|
||||
setTimeout(() => (this.shareStatus = ''), 3000);
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
<div class="blog-list">
|
||||
<section class="blog-list__hero surface-card">
|
||||
<div>
|
||||
<span class="blog-list__kicker">Community-Blogs</span>
|
||||
<h1>Blogs</h1>
|
||||
<p>Artikel, Projektstände und persönliche Einblicke aus der YourPart-Community.</p>
|
||||
<span class="blog-list__kicker">{{ $t('blog.list.eyebrow') }}</span>
|
||||
<h1>{{ $t('blog.list.title') }}</h1>
|
||||
<p>{{ $t('blog.list.intro') }}</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<router-link v-if="$store.getters.isLoggedIn" class="btn" to="/blogs/create">Neuen Blog erstellen</router-link>
|
||||
<router-link v-if="$store.getters.isLoggedIn" class="btn" to="/blogs/create">{{ $t('blog.list.create') }}</router-link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="loading" class="blog-list__state surface-card">Laden…</div>
|
||||
<div v-else-if="!blogs.length" class="blog-list__state surface-card">Keine Blogs gefunden.</div>
|
||||
<div v-if="loading" class="blog-list__state surface-card">{{ $t('blog.list.loading') }}</div>
|
||||
<div v-else-if="!blogs.length" class="blog-list__state surface-card">{{ $t('blog.list.empty') }}</div>
|
||||
<div v-else class="blog-grid">
|
||||
<article v-for="b in blogs" :key="b.id" class="blog-card surface-card">
|
||||
<div class="blog-card__meta">von {{ b.owner?.username || 'Unbekannt' }}</div>
|
||||
<div class="blog-card__meta">{{ $t('blog.list.by') }} {{ b.owner?.username || $t('blog.list.unknownAuthor') }}</div>
|
||||
<h2><router-link :to="blogUrl(b)">{{ b.title }}</router-link></h2>
|
||||
<p>{{ blogExcerpt(b) }}</p>
|
||||
<router-link class="blog-card__link" :to="blogUrl(b)">Zum Blog</router-link>
|
||||
<router-link class="blog-card__link" :to="blogUrl(b)">{{ $t('blog.list.open') }}</router-link>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
return slug ? `/blogs/${encodeURIComponent(slug)}` : `/blogs/${blog.id}`;
|
||||
},
|
||||
blogExcerpt(blog) {
|
||||
const source = blog?.description || 'Öffentliche Einträge, Gedanken und Projektstände aus der Community.';
|
||||
const source = blog?.description || this.$t('blog.list.fallbackExcerpt');
|
||||
return source.length > 150 ? `${source.slice(0, 147)}...` : source;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="blog-view">
|
||||
<div v-if="loading" class="blog-view__state surface-card">Laden…</div>
|
||||
<div v-if="loading" class="blog-view__state surface-card">{{ $t('blog.view.loading') }}</div>
|
||||
<div v-else-if="blog" class="blog-layout">
|
||||
<section class="blog-hero surface-card">
|
||||
<div>
|
||||
@@ -9,16 +9,16 @@
|
||||
<p v-if="blog.description" class="blog-description">{{ blog.description }}</p>
|
||||
</div>
|
||||
<div v-if="$store.getters.isLoggedIn" class="actions">
|
||||
<router-link class="editbutton" v-if="isOwner" :to="{ name: 'BlogEdit', params: { id: blog.id } }">Bearbeiten</router-link>
|
||||
<router-link class="editbutton" v-if="isOwner" :to="{ name: 'BlogEdit', params: { id: blog.id } }">{{ $t('blog.view.edit') }}</router-link>
|
||||
</div>
|
||||
</section>
|
||||
<div class="blog-content">
|
||||
<section class="posts surface-card">
|
||||
<div class="posts__header">
|
||||
<h2>{{ $t('blog.posts') }}</h2>
|
||||
<span class="posts__count">{{ total }} Einträge</span>
|
||||
<span class="posts__count">{{ $t('blog.view.entriesCount', { count: total }) }}</span>
|
||||
</div>
|
||||
<div v-if="!items.length" class="blog-view__state">Keine Einträge vorhanden.</div>
|
||||
<div v-if="!items.length" class="blog-view__state">{{ $t('blog.view.empty') }}</div>
|
||||
<article v-for="p in items" :key="p.id" class="post">
|
||||
<h3>{{ p.title }}</h3>
|
||||
<div class="content" v-html="sanitize(p.content)" />
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
.map((item) => `${item.title || ''} ${stripHtml(item.content || '')}`.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const summarySource = this.blog.description || plainTextPosts || 'Öffentlicher Community-Blog auf YourPart.';
|
||||
const summarySource = this.blog.description || plainTextPosts || this.$t('blog.view.fallbackDescription');
|
||||
const description = truncateText(summarySource, 160);
|
||||
const canonicalPath = this.canonicalBlogPath();
|
||||
|
||||
@@ -146,8 +146,8 @@ export default {
|
||||
console.error('Blog konnte nicht geladen werden:', e);
|
||||
// this.$router.replace('/blogs');
|
||||
applySeo({
|
||||
title: 'Blog nicht gefunden | YourPart',
|
||||
description: 'Der angeforderte Blog konnte nicht geladen werden.',
|
||||
title: this.$t('blog.view.notFoundTitle'),
|
||||
description: this.$t('blog.view.notFoundDescription'),
|
||||
canonicalPath: '/blogs',
|
||||
robots: 'noindex, nofollow',
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="account-settings">
|
||||
<section class="account-settings__hero surface-card">
|
||||
<span class="account-settings__eyebrow">Einstellungen</span>
|
||||
<span class="account-settings__eyebrow">{{ $t("settings.account.heroEyebrow") }}</span>
|
||||
<h2>{{ $t("settings.account.title") }}</h2>
|
||||
<p>Benutzername, E-Mail, Passwort und Sichtbarkeit an einer Stelle pflegen.</p>
|
||||
<p>{{ $t("settings.account.heroIntro") }}</p>
|
||||
</section>
|
||||
|
||||
<section class="account-settings__panel surface-card">
|
||||
@@ -22,7 +22,7 @@
|
||||
<span>{{ $t("settings.account.newpassword") }}</span>
|
||||
<input type="password" v-model="newpassword" :placeholder="$t('settings.account.newpassword')"
|
||||
autocomplete="new-password" :class="{ 'field-error': newpassword && !isNewPasswordValid }" />
|
||||
<span v-if="newpassword && !isNewPasswordValid" class="form-error">Das neue Passwort sollte mindestens 8 Zeichen haben.</span>
|
||||
<span v-if="newpassword && !isNewPasswordValid" class="form-error">{{ $t("settings.account.validation.newPasswordTooShort") }}</span>
|
||||
</label>
|
||||
|
||||
<label class="account-settings__field">
|
||||
@@ -30,14 +30,14 @@
|
||||
<input type="password" v-model="newpasswordretype"
|
||||
:placeholder="$t('settings.account.newpasswordretype')" autocomplete="new-password"
|
||||
:class="{ 'field-error': newpasswordretype && !passwordsMatch }" />
|
||||
<span v-if="newpasswordretype && !passwordsMatch" class="form-error">Die Passwörter stimmen nicht überein.</span>
|
||||
<span v-if="newpasswordretype && !passwordsMatch" class="form-error">{{ $t("settings.account.validation.passwordMismatch") }}</span>
|
||||
</label>
|
||||
|
||||
<label class="account-settings__field account-settings__field--full">
|
||||
<span>{{ $t("settings.account.oldpassword") }}</span>
|
||||
<input type="password" v-model="oldpassword" :placeholder="$t('settings.account.oldpassword')"
|
||||
autocomplete="current-password" :class="{ 'field-error': requiresOldPassword && !oldpassword.trim() }" />
|
||||
<span v-if="requiresOldPassword && !oldpassword.trim()" class="form-error">Zum Passwortwechsel wird das aktuelle Passwort benötigt.</span>
|
||||
<span v-if="requiresOldPassword && !oldpassword.trim()" class="form-error">{{ $t("settings.account.validation.oldPasswordRequired") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -149,18 +149,18 @@ export default {
|
||||
|
||||
if (hasNewPassword) {
|
||||
if (!this.isNewPasswordValid) {
|
||||
showError(this, 'Das neue Passwort ist noch zu kurz.');
|
||||
showError(this, 'tr:settings.account.validation.newPasswordTooShort');
|
||||
return;
|
||||
}
|
||||
// Validiere Passwort-Wiederholung nur wenn ein neues Passwort eingegeben wurde
|
||||
if (!this.passwordsMatch) {
|
||||
showError(this, 'Die Passwörter stimmen nicht überein.');
|
||||
showError(this, 'tr:settings.account.validation.passwordMismatch');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe ob das alte Passwort eingegeben wurde
|
||||
if (!this.oldpassword || this.oldpassword.trim() === '') {
|
||||
showError(this, 'Bitte geben Sie Ihr aktuelles Passwort ein, um das Passwort zu ändern.');
|
||||
showError(this, 'tr:settings.account.validation.oldPasswordRequired');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export default {
|
||||
// API-Aufruf zum Speichern der Account-Einstellungen
|
||||
await apiClient.post('/api/settings/set-account', accountData);
|
||||
|
||||
showSuccess(this, 'Account-Einstellungen erfolgreich gespeichert.');
|
||||
showSuccess(this, 'tr:settings.account.feedback.saved');
|
||||
|
||||
// Leere die Passwort-Felder nach erfolgreichem Speichern
|
||||
this.newpassword = '';
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern der Account-Einstellungen:', error);
|
||||
showApiError(this, error, 'Ein Fehler ist beim Speichern der Account-Einstellungen aufgetreten.');
|
||||
showApiError(this, error, 'tr:settings.account.feedback.saveError');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -188,6 +188,7 @@ export default {
|
||||
try {
|
||||
// Mappe UI-Sprache zu vocab_language Name
|
||||
const languageMap = {
|
||||
'ceb': 'Bisaya',
|
||||
'de': 'Deutsch',
|
||||
'en': 'Englisch',
|
||||
'es': 'Spanisch',
|
||||
@@ -489,51 +490,45 @@ export default {
|
||||
|
||||
.course-actions button {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
transition: background-color 0.2s, border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.course-actions button:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #bbb;
|
||||
background: var(--color-primary-hover);
|
||||
box-shadow: 0 10px 18px rgba(248, 162, 43, 0.22);
|
||||
}
|
||||
|
||||
.btn-enroll {
|
||||
background: #4CAF50 !important;
|
||||
color: white !important;
|
||||
border-color: #4CAF50 !important;
|
||||
}
|
||||
|
||||
.btn-enroll:hover {
|
||||
background: #45a049 !important;
|
||||
border-color: #45a049 !important;
|
||||
background: var(--color-primary) !important;
|
||||
color: #2b1f14 !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.btn-continue {
|
||||
background: #2196F3 !important;
|
||||
color: white !important;
|
||||
border-color: #2196F3 !important;
|
||||
}
|
||||
|
||||
.btn-continue:hover {
|
||||
background: #0b7dda !important;
|
||||
border-color: #0b7dda !important;
|
||||
background: var(--color-primary) !important;
|
||||
color: #2b1f14 !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #FF9800 !important;
|
||||
color: white !important;
|
||||
border-color: #FF9800 !important;
|
||||
background: rgba(255, 255, 255, 0.9) !important;
|
||||
color: var(--color-text-primary) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: #e68900 !important;
|
||||
border-color: #e68900 !important;
|
||||
background: rgba(255, 255, 255, 0.98) !important;
|
||||
border-color: var(--color-border-strong) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dialog-overlay {
|
||||
|
||||
@@ -526,20 +526,20 @@ export default {
|
||||
|
||||
.btn-current-lesson {
|
||||
padding: 12px 24px;
|
||||
background: var(--color-primary-orange);
|
||||
color: #000000;
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
transition: background 0.05s;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
|
||||
}
|
||||
|
||||
.btn-current-lesson:hover {
|
||||
background: #FFF4F0;
|
||||
color: #5D4037;
|
||||
border: 1px solid #5D4037;
|
||||
background: var(--color-primary-hover);
|
||||
box-shadow: 0 10px 18px rgba(248, 162, 43, 0.22);
|
||||
}
|
||||
|
||||
.lesson-cards {
|
||||
@@ -675,20 +675,20 @@ export default {
|
||||
|
||||
.btn-start {
|
||||
padding: 8px 16px;
|
||||
background: var(--color-primary-orange);
|
||||
color: #000000;
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
transition: background 0.05s;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
|
||||
}
|
||||
|
||||
.btn-start:hover:not(:disabled) {
|
||||
background: #FFF4F0;
|
||||
color: #5D4037;
|
||||
border: 1px solid #5D4037;
|
||||
background: var(--color-primary-hover);
|
||||
box-shadow: 0 10px 18px rgba(248, 162, 43, 0.22);
|
||||
}
|
||||
|
||||
.btn-start:disabled {
|
||||
@@ -701,26 +701,26 @@ export default {
|
||||
|
||||
.btn-edit {
|
||||
padding: 6px 12px;
|
||||
background: var(--color-primary-orange);
|
||||
color: #000000;
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
transition: background 0.05s;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: #FFF4F0;
|
||||
color: #5D4037;
|
||||
border: 1px solid #5D4037;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
padding: 6px 12px;
|
||||
background: #dc3545;
|
||||
background: rgba(177, 59, 53, 0.92);
|
||||
color: white;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
@@ -728,7 +728,7 @@ export default {
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #c82333;
|
||||
background: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
.dialog-overlay {
|
||||
|
||||
@@ -947,6 +947,14 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.activeTab = 'exercises';
|
||||
this.$nextTick(() => {
|
||||
const scrollEl = document.querySelector('.app-content__scroll.contentscroll');
|
||||
if (scrollEl) {
|
||||
scrollEl.scrollTop = 0;
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateExerciseUnlockState() {
|
||||
if (this.exercisePreparationCompleted) {
|
||||
@@ -2353,10 +2361,12 @@ export default {
|
||||
|
||||
.btn-back {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.lesson-description {
|
||||
@@ -2447,18 +2457,20 @@ export default {
|
||||
|
||||
.exercise-item button {
|
||||
padding: 10px 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
margin-top: 10px;
|
||||
transition: background-color 0.2s;
|
||||
transition: background-color 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
|
||||
}
|
||||
|
||||
.exercise-item button:hover:not(:disabled) {
|
||||
background: #45a049;
|
||||
background: var(--color-primary-hover);
|
||||
box-shadow: 0 10px 18px rgba(248, 162, 43, 0.22);
|
||||
}
|
||||
|
||||
.exercise-item button:disabled {
|
||||
@@ -2513,24 +2525,26 @@ export default {
|
||||
|
||||
.tab-button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
color: #666;
|
||||
color: var(--color-text-secondary);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.2s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tab-button:hover:not(:disabled) {
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
color: var(--color-text-primary);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #007bff;
|
||||
border-bottom-color: #007bff;
|
||||
color: #2b1f14;
|
||||
background: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -2687,17 +2701,18 @@ export default {
|
||||
|
||||
.btn-start-trainer {
|
||||
padding: 10px 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 6px 14px rgba(248, 162, 43, 0.18);
|
||||
}
|
||||
|
||||
.btn-start-trainer:hover {
|
||||
background: #45a049;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.vocab-trainer-stats {
|
||||
@@ -2733,8 +2748,8 @@ export default {
|
||||
}
|
||||
|
||||
.mode-badge.mode-active {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -2745,16 +2760,16 @@ export default {
|
||||
|
||||
.btn-stop-trainer {
|
||||
padding: 5px 15px;
|
||||
background: #dc3545;
|
||||
background: rgba(177, 59, 53, 0.92);
|
||||
color: white;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.btn-stop-trainer:hover {
|
||||
background: #c82333;
|
||||
background: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
.vocab-question {
|
||||
@@ -2802,20 +2817,20 @@ export default {
|
||||
|
||||
.btn-switch-mode {
|
||||
padding: 8px 16px;
|
||||
background: var(--color-primary-orange);
|
||||
color: #000000;
|
||||
border: 1px solid var(--color-primary-orange);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
transition: background 0.05s;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-switch-mode:hover {
|
||||
background: var(--color-primary-orange-light);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border: 1px solid var(--color-border-strong);
|
||||
}
|
||||
|
||||
.vocab-answer-area.multiple-choice {
|
||||
@@ -2842,14 +2857,14 @@ export default {
|
||||
}
|
||||
|
||||
.choice-button:hover {
|
||||
border-color: #007bff;
|
||||
background: #f0f8ff;
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(248, 162, 43, 0.08);
|
||||
}
|
||||
|
||||
.choice-button.selected {
|
||||
border-color: #007bff;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
}
|
||||
|
||||
.vocab-input {
|
||||
@@ -2862,16 +2877,16 @@ export default {
|
||||
|
||||
.btn-check {
|
||||
padding: 10px 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.btn-check:hover:not(:disabled) {
|
||||
background: #45a049;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-check:disabled {
|
||||
@@ -2903,16 +2918,16 @@ export default {
|
||||
|
||||
.vocab-next button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.vocab-next button:hover {
|
||||
background: #0056b3;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.vocab-info-text {
|
||||
@@ -2936,9 +2951,9 @@ export default {
|
||||
|
||||
.btn-continue {
|
||||
padding: 12px 24px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
@@ -2946,7 +2961,7 @@ export default {
|
||||
}
|
||||
|
||||
.btn-continue:hover {
|
||||
background: #0056b3;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Reading Aloud & Speaking From Memory Styles */
|
||||
@@ -2981,12 +2996,12 @@ export default {
|
||||
}
|
||||
|
||||
.btn-record {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
}
|
||||
|
||||
.btn-record:hover:not(:disabled) {
|
||||
background: #218838;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-record:disabled {
|
||||
@@ -2995,26 +3010,26 @@ export default {
|
||||
}
|
||||
|
||||
.btn-stop-record {
|
||||
background: #dc3545;
|
||||
background: rgba(177, 59, 53, 0.92);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-stop-record:hover {
|
||||
background: #c82333;
|
||||
background: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
.btn-check {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.btn-check:hover {
|
||||
background: #0056b3;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.recording-status {
|
||||
@@ -3150,16 +3165,16 @@ export default {
|
||||
|
||||
.dialog-button {
|
||||
padding: 8px 16px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
background: var(--color-primary);
|
||||
color: #2b1f14;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.dialog-button:hover {
|
||||
background: #0056b3;
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
|
||||
Reference in New Issue
Block a user