1429 lines
38 KiB
Vue
1429 lines
38 KiB
Vue
<template>
|
||
<div class="vocabulary-layout">
|
||
<!-- 主內容區 - 多列布局 (依據 function-specs) -->
|
||
<div class="main-content">
|
||
<!-- 頁面標題區域 -->
|
||
<div class="page-header">
|
||
<div class="header-section">
|
||
<div class="header-text">
|
||
<h1>詞彙學習 - {{ currentWord?.text || '選擇詞彙' }}</h1>
|
||
<p>探索新詞彙,掌握語言精髓</p>
|
||
</div>
|
||
|
||
<div class="header-stats">
|
||
<div class="stat-item">
|
||
<span class="stat-value">{{ userProgress.learnedCount }}</span>
|
||
<span class="stat-label">已學詞彙</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-value">{{ Math.round(currentWordProgress) }}%</span>
|
||
<span class="stat-label">學習進度</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 多列布局容器 (依據 function-specs) -->
|
||
<div class="vocabulary-content">
|
||
<!-- 左側:詞彙資訊主區域 -->
|
||
<div class="vocabulary-main">
|
||
<!-- 詞彙介紹卡片 -->
|
||
<div class="vocabulary-section">
|
||
<div class="vocabulary-card" v-if="currentWord">
|
||
<!-- 目標詞彙文字 (大字體標題) -->
|
||
<div class="vocabulary-word">{{ currentWord.text }}</div>
|
||
|
||
<!-- 音標顯示 (支援點擊複製) -->
|
||
<div
|
||
class="vocabulary-phonetic"
|
||
@click="copyPhonetic"
|
||
:title="'點擊複製音標: ' + currentWord.phonetic"
|
||
>
|
||
{{ currentWord.phonetic }}
|
||
<QIcon name="content_copy" class="copy-icon" />
|
||
</div>
|
||
|
||
<!-- 詞性標記 (色彩編碼) -->
|
||
<div class="part-of-speech">
|
||
<span
|
||
v-for="pos in currentWord.parts_of_speech"
|
||
:key="pos"
|
||
class="pos-tag"
|
||
:class="`pos-${pos}`"
|
||
>
|
||
{{ pos }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 詞頻統計 (1-5星評級) -->
|
||
<div class="frequency-rating">
|
||
<span class="frequency-label">使用頻率:</span>
|
||
<div class="stars">
|
||
<QIcon
|
||
v-for="i in 5"
|
||
:key="i"
|
||
name="star"
|
||
:class="{ active: i <= (currentWord.frequency_rating || 0) }"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 中文定義 (主要定義區域) -->
|
||
<div class="vocabulary-definition">
|
||
{{ currentWord.definition }}
|
||
</div>
|
||
|
||
<!-- 英文定義 (進階模式,可切換) -->
|
||
<div v-if="showEnglishDefinition && currentWord.definition_english" class="vocabulary-definition-en">
|
||
<strong>English:</strong> {{ currentWord.definition_english }}
|
||
</div>
|
||
|
||
<!-- 使用情境說明 (獨立區塊) -->
|
||
<div v-if="currentWord.usage_context" class="usage-context">
|
||
<h4>使用情境</h4>
|
||
<p>{{ currentWord.usage_context }}</p>
|
||
</div>
|
||
|
||
<!-- 多例句並列顯示 -->
|
||
<div class="examples-section">
|
||
<h4>例句</h4>
|
||
<div
|
||
v-for="(example, index) in currentWord.examples"
|
||
:key="index"
|
||
class="vocabulary-example"
|
||
>
|
||
<div class="example-text">{{ example.text }}</div>
|
||
<div class="example-translation">{{ example.translation }}</div>
|
||
<button
|
||
class="example-audio-btn"
|
||
@click="playExampleAudio(example, index + 1)"
|
||
:title="`播放例句 ${index + 1} (快捷鍵: ${index + 1})`"
|
||
>
|
||
<QIcon name="volume_up" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 控制按鈕區域 -->
|
||
<div class="vocabulary-controls">
|
||
<!-- 發音播放按鈕 (Space) -->
|
||
<button
|
||
class="control-btn"
|
||
@click="playAudio"
|
||
:disabled="audioLoading"
|
||
:title="'播放發音 (Space)'"
|
||
>
|
||
<QIcon :name="audioLoading ? 'hourglass_empty' : 'volume_up'" />
|
||
播放發音
|
||
</button>
|
||
|
||
<!-- 慢速發音按鈕 (Shift+Space) -->
|
||
<button
|
||
class="control-btn"
|
||
@click="playSlowAudio"
|
||
:disabled="audioLoading"
|
||
:title="'慢速播放 (Shift+Space)'"
|
||
>
|
||
<QIcon name="slow_motion_video" />
|
||
慢速播放
|
||
</button>
|
||
|
||
<!-- 收藏按鈕 (Ctrl+D) -->
|
||
<button
|
||
class="control-btn"
|
||
:class="{ bookmarked: isBookmarked }"
|
||
@click="toggleBookmark"
|
||
:title="'收藏詞彙 (Ctrl+D)'"
|
||
>
|
||
<QIcon :name="isBookmarked ? 'bookmark' : 'bookmark_border'" />
|
||
{{ isBookmarked ? '已收藏' : '收藏' }}
|
||
</button>
|
||
|
||
<!-- 詞典查詢按鈕 (F1) -->
|
||
<button
|
||
class="control-btn"
|
||
@click="openDictionary"
|
||
@contextmenu.prevent="showDictionaryMenu"
|
||
:title="'查詢外部詞典 (F1 或右鍵)'"
|
||
>
|
||
<QIcon name="menu_book" />
|
||
詞典查詢
|
||
</button>
|
||
|
||
<!-- 開始練習按鈕 (Enter) -->
|
||
<button
|
||
class="control-btn primary"
|
||
@click="startPractice"
|
||
:title="'開始練習 (Enter)'"
|
||
>
|
||
<QIcon name="play_arrow" />
|
||
開始練習
|
||
</button>
|
||
|
||
<!-- 跳過介紹按鈕 (Shift+Enter) -->
|
||
<button
|
||
class="control-btn secondary"
|
||
@click="skipIntroduction"
|
||
:title="'跳過介紹 (Shift+Enter)'"
|
||
>
|
||
<QIcon name="skip_next" />
|
||
跳過介紹
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 學習進度條 -->
|
||
<div class="progress-section">
|
||
<div class="progress-label">學習進度: {{ Math.round(currentWordProgress) }}%</div>
|
||
<QLinearProgress
|
||
:value="currentWordProgress / 100"
|
||
color="teal"
|
||
track-color="grey-3"
|
||
size="md"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 筆記編輯器區域 (支援Markdown,可摺疊) -->
|
||
<div class="notes-section" :class="{ expanded: notesExpanded }">
|
||
<div class="notes-header" @click="toggleNotes">
|
||
<h4>
|
||
<QIcon name="edit_note" />
|
||
學習筆記 (Ctrl+N)
|
||
</h4>
|
||
<QIcon :name="notesExpanded ? 'expand_less' : 'expand_more'" />
|
||
</div>
|
||
|
||
<Transition name="slide-down">
|
||
<div v-show="notesExpanded" class="notes-content">
|
||
<QInput
|
||
v-model="userNotes"
|
||
type="textarea"
|
||
placeholder="在此記錄學習重點,支援 Markdown 格式..."
|
||
rows="6"
|
||
outlined
|
||
class="notes-editor"
|
||
/>
|
||
<div class="notes-actions">
|
||
<QBtn
|
||
flat
|
||
dense
|
||
size="sm"
|
||
icon="preview"
|
||
label="預覽"
|
||
@click="toggleNotesPreview"
|
||
/>
|
||
<QBtn
|
||
flat
|
||
dense
|
||
size="sm"
|
||
icon="save"
|
||
label="保存"
|
||
@click="saveNotes"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Markdown 預覽 -->
|
||
<div v-if="showNotesPreview" class="notes-preview" v-html="renderedNotes"></div>
|
||
</div>
|
||
</Transition>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右側:相關詞彙和例句面板 (依據 function-specs) -->
|
||
<div class="vocabulary-sidebar">
|
||
<!-- 相關詞彙推薦 -->
|
||
<div class="related-words-section">
|
||
<h4>相關詞彙</h4>
|
||
<div class="related-words-list">
|
||
<button
|
||
v-for="relatedWord in currentWord?.related_words || []"
|
||
:key="relatedWord.id"
|
||
class="related-word-item"
|
||
@click="selectRelatedWord(relatedWord)"
|
||
@click.middle="openInNewTab(relatedWord)"
|
||
@contextmenu.prevent="openInNewTab(relatedWord)"
|
||
:title="`點擊學習,Ctrl+Click 或右鍵新標籤開啟`"
|
||
>
|
||
<div class="related-word-text">{{ relatedWord.text }}</div>
|
||
<div class="related-word-def">{{ relatedWord.definition }}</div>
|
||
<QIcon name="open_in_new" class="new-tab-icon" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 詞彙清單 -->
|
||
<div class="vocabulary-list">
|
||
<div class="list-header">
|
||
<div class="list-title">詞彙清單</div>
|
||
<div class="filter-tabs">
|
||
<button
|
||
v-for="filter in filterOptions"
|
||
:key="filter.value"
|
||
class="filter-tab"
|
||
:class="{ active: currentFilter === filter.value }"
|
||
@click="setFilter(filter.value)"
|
||
>
|
||
{{ filter.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vocabulary-items-container">
|
||
<div
|
||
v-for="word in filteredWords"
|
||
:key="word.id"
|
||
class="vocabulary-item"
|
||
:class="{ active: currentWord?.id === word.id }"
|
||
@click="selectWord(word)"
|
||
>
|
||
<div class="word-info">
|
||
<div class="word-text">
|
||
<div class="word-main">{{ word.text }}</div>
|
||
<div class="word-definition">{{ word.definition }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="word-status">
|
||
<div
|
||
class="mastery-indicator"
|
||
:class="getMasteryClass(word.mastery_level)"
|
||
></div>
|
||
<button
|
||
class="play-btn"
|
||
@click.stop="playWordAudio(word)"
|
||
:title="`播放 ${word.text} 發音`"
|
||
>
|
||
<QIcon name="volume_up" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useQuasar } from 'quasar'
|
||
import { useAudio } from '@/composables/useAudio'
|
||
|
||
// 類型定義 (依據 function-specs)
|
||
interface VocabularyWord {
|
||
id: string
|
||
text: string
|
||
phonetic: string
|
||
definition: string
|
||
definition_english?: string
|
||
parts_of_speech: string[]
|
||
frequency_rating?: number
|
||
usage_context?: string
|
||
examples: ExampleSentence[]
|
||
audio_url?: string
|
||
difficulty_level?: number
|
||
mastery_level?: number
|
||
related_words?: RelatedWord[]
|
||
user_notes?: string
|
||
}
|
||
|
||
interface ExampleSentence {
|
||
text: string
|
||
translation: string
|
||
audio_url?: string
|
||
}
|
||
|
||
interface RelatedWord {
|
||
id: string
|
||
text: string
|
||
definition: string
|
||
}
|
||
|
||
interface FilterOption {
|
||
value: string
|
||
label: string
|
||
}
|
||
|
||
// 狀態管理
|
||
const router = useRouter()
|
||
const $q = useQuasar()
|
||
const { quickPlay, isLoading: audioLoading } = useAudio()
|
||
|
||
const currentFilter = ref('all')
|
||
const currentWordIndex = ref(0)
|
||
const notesExpanded = ref(false)
|
||
const showNotesPreview = ref(false)
|
||
const userNotes = ref('')
|
||
const isBookmarked = ref(false)
|
||
const showEnglishDefinition = ref(false)
|
||
|
||
// 過濾選項
|
||
const filterOptions: FilterOption[] = [
|
||
{ value: 'all', label: '全部' },
|
||
{ value: 'learning', label: '學習中' },
|
||
{ value: 'learned', label: '已掌握' },
|
||
{ value: 'difficult', label: '困難' }
|
||
]
|
||
|
||
// 模擬詞彙數據 (依據 function-specs 欄位細節)
|
||
const vocabularyWords = ref<VocabularyWord[]>([
|
||
{
|
||
id: '1',
|
||
text: 'sophisticated',
|
||
phonetic: '/səˈfɪstɪkeɪtɪd/',
|
||
definition: '複雜的;精密的;老練的;世故的',
|
||
definition_english: 'Having great knowledge or experience; complex and refined',
|
||
parts_of_speech: ['adj.'],
|
||
frequency_rating: 4,
|
||
usage_context: '常用於描述技術、系統、人的品味或行為的複雜性和精細程度',
|
||
examples: [
|
||
{
|
||
text: 'She has sophisticated tastes in art.',
|
||
translation: '她在藝術方面品味很高雅。',
|
||
audio_url: '/audio/sophisticated_example1.mp3'
|
||
},
|
||
{
|
||
text: 'The sophisticated security system protects the building.',
|
||
translation: '精密的安全系統保護著這棟建築。',
|
||
audio_url: '/audio/sophisticated_example2.mp3'
|
||
}
|
||
],
|
||
audio_url: '/audio/sophisticated.mp3',
|
||
difficulty_level: 3,
|
||
mastery_level: 60,
|
||
related_words: [
|
||
{ id: '4', text: 'complex', definition: '複雜的' },
|
||
{ id: '5', text: 'refined', definition: '精緻的' },
|
||
{ id: '6', text: 'advanced', definition: '先進的' }
|
||
],
|
||
user_notes: '## 學習重點\n- 可用於描述人或事物\n- 褒義詞,表示正面的複雜性'
|
||
},
|
||
{
|
||
id: '2',
|
||
text: 'benevolent',
|
||
phonetic: '/bəˈnevələnt/',
|
||
definition: '仁慈的;善意的;慈善的',
|
||
definition_english: 'Well meaning and kindly; charitable',
|
||
parts_of_speech: ['adj.'],
|
||
frequency_rating: 3,
|
||
usage_context: '正式用語,常用於描述統治者、組織或個人的善意行為',
|
||
examples: [
|
||
{
|
||
text: 'The benevolent king helped the poor.',
|
||
translation: '仁慈的國王幫助貧民。',
|
||
audio_url: '/audio/benevolent_example1.mp3'
|
||
}
|
||
],
|
||
audio_url: '/audio/benevolent.mp3',
|
||
difficulty_level: 2,
|
||
mastery_level: 80,
|
||
related_words: [
|
||
{ id: '7', text: 'kind', definition: '善良的' },
|
||
{ id: '8', text: 'charitable', definition: '慈善的' }
|
||
]
|
||
},
|
||
{
|
||
id: '3',
|
||
text: 'meticulous',
|
||
phonetic: '/məˈtɪkjələs/',
|
||
definition: '一絲不苟的;細心的;謹慎的',
|
||
definition_english: 'Showing great attention to detail; very careful and precise',
|
||
parts_of_speech: ['adj.'],
|
||
frequency_rating: 3,
|
||
usage_context: '用於描述工作態度、行為方式,強調注重細節和精確性',
|
||
examples: [
|
||
{
|
||
text: 'She is meticulous about her work.',
|
||
translation: '她對工作一絲不苟。',
|
||
audio_url: '/audio/meticulous_example1.mp3'
|
||
}
|
||
],
|
||
audio_url: '/audio/meticulous.mp3',
|
||
difficulty_level: 3,
|
||
mastery_level: 30,
|
||
related_words: [
|
||
{ id: '9', text: 'careful', definition: '小心的' },
|
||
{ id: '10', text: 'precise', definition: '精確的' }
|
||
]
|
||
}
|
||
])
|
||
|
||
// 計算屬性
|
||
const currentWord = computed(() => {
|
||
return vocabularyWords.value[currentWordIndex.value] || null
|
||
})
|
||
|
||
const currentWordProgress = computed(() => {
|
||
return currentWord.value?.mastery_level || 0
|
||
})
|
||
|
||
const filteredWords = computed(() => {
|
||
const words = vocabularyWords.value
|
||
|
||
switch (currentFilter.value) {
|
||
case 'learning':
|
||
return words.filter(w => (w.mastery_level || 0) < 80 && (w.mastery_level || 0) > 0)
|
||
case 'learned':
|
||
return words.filter(w => (w.mastery_level || 0) >= 80)
|
||
case 'difficult':
|
||
return words.filter(w => (w.difficulty_level || 1) >= 3)
|
||
default:
|
||
return words
|
||
}
|
||
})
|
||
|
||
const userProgress = computed(() => ({
|
||
learnedCount: vocabularyWords.value.filter(w => (w.mastery_level || 0) >= 80).length,
|
||
todayCount: 5 // 模擬今日學習數量
|
||
}))
|
||
|
||
// 筆記相關 (Markdown 渲染)
|
||
const renderedNotes = computed(() => {
|
||
if (!userNotes.value) return ''
|
||
// 簡單的 Markdown 渲染 (實際會使用專門的 Markdown 庫)
|
||
return userNotes.value
|
||
.replace(/^### (.+$)/gim, '<h3>$1</h3>')
|
||
.replace(/^## (.+$)/gim, '<h2>$1</h2>')
|
||
.replace(/^# (.+$)/gim, '<h1>$1</h1>')
|
||
.replace(/\*\*(.+)\*\*/gim, '<strong>$1</strong>')
|
||
.replace(/\*(.+)\*/gim, '<em>$1</em>')
|
||
.replace(/\n/gim, '<br>')
|
||
})
|
||
|
||
// 方法
|
||
const selectWord = (word: VocabularyWord) => {
|
||
const index = vocabularyWords.value.findIndex(w => w.id === word.id)
|
||
if (index !== -1) {
|
||
currentWordIndex.value = index
|
||
// 載入用戶筆記
|
||
userNotes.value = word.user_notes || ''
|
||
// 載入收藏狀態 (模擬)
|
||
isBookmarked.value = Math.random() > 0.5
|
||
}
|
||
}
|
||
|
||
const selectRelatedWord = (word: RelatedWord) => {
|
||
// 模擬導航到相關詞彙
|
||
const fullWord = vocabularyWords.value.find(w => w.id === word.id)
|
||
if (fullWord) {
|
||
selectWord(fullWord)
|
||
}
|
||
}
|
||
|
||
const openInNewTab = (word: RelatedWord) => {
|
||
// 實現新標籤開啟功能 (依據 function-specs)
|
||
const url = `/learning/vocabulary/introduction/${word.id}`
|
||
window.open(url, '_blank')
|
||
}
|
||
|
||
const setFilter = (filterValue: string) => {
|
||
currentFilter.value = filterValue
|
||
}
|
||
|
||
const getMasteryClass = (masteryLevel?: number): string => {
|
||
const level = masteryLevel || 0
|
||
if (level >= 80) return 'learned'
|
||
if (level > 0) return 'learning'
|
||
return ''
|
||
}
|
||
|
||
// 音頻播放相關
|
||
const playAudio = async () => {
|
||
if (currentWord.value?.audio_url) {
|
||
await quickPlay(currentWord.value.audio_url)
|
||
}
|
||
}
|
||
|
||
const playSlowAudio = async () => {
|
||
if (currentWord.value?.audio_url) {
|
||
await quickPlay(currentWord.value.audio_url, { playbackRate: 0.75 })
|
||
}
|
||
}
|
||
|
||
const playExampleAudio = async (example: ExampleSentence, index: number) => {
|
||
if (example.audio_url) {
|
||
await quickPlay(example.audio_url)
|
||
}
|
||
}
|
||
|
||
const playWordAudio = async (word: VocabularyWord) => {
|
||
if (word.audio_url) {
|
||
await quickPlay(word.audio_url)
|
||
}
|
||
}
|
||
|
||
// 筆記功能 (依據 function-specs)
|
||
const toggleNotes = () => {
|
||
notesExpanded.value = !notesExpanded.value
|
||
}
|
||
|
||
const toggleNotesPreview = () => {
|
||
showNotesPreview.value = !showNotesPreview.value
|
||
}
|
||
|
||
const saveNotes = () => {
|
||
if (currentWord.value) {
|
||
currentWord.value.user_notes = userNotes.value
|
||
$q.notify({
|
||
type: 'positive',
|
||
message: '筆記已保存',
|
||
position: 'top-right'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 書籤功能 (依據 function-specs)
|
||
const toggleBookmark = () => {
|
||
isBookmarked.value = !isBookmarked.value
|
||
|
||
if (isBookmarked.value) {
|
||
// 實際會整合瀏覽器書籤 API
|
||
$q.notify({
|
||
type: 'positive',
|
||
message: '已添加到書籤',
|
||
position: 'top-right'
|
||
})
|
||
} else {
|
||
$q.notify({
|
||
type: 'info',
|
||
message: '已從書籤移除',
|
||
position: 'top-right'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 詞典整合功能 (依據 function-specs)
|
||
const openDictionary = () => {
|
||
if (currentWord.value) {
|
||
const word = encodeURIComponent(currentWord.value.text)
|
||
// 默認使用 Cambridge Dictionary
|
||
window.open(`https://dictionary.cambridge.org/search/english/direct/?q=${word}`, '_blank')
|
||
}
|
||
}
|
||
|
||
const showDictionaryMenu = () => {
|
||
// 顯示詞典選擇菜單
|
||
const dictionaries = [
|
||
{ name: 'Cambridge', url: 'https://dictionary.cambridge.org/search/english/direct/?q=' },
|
||
{ name: 'Oxford', url: 'https://www.oxfordlearnersdictionaries.com/definition/english/' },
|
||
{ name: 'Merriam-Webster', url: 'https://www.merriam-webster.com/dictionary/' }
|
||
]
|
||
|
||
// 這裡會顯示上下文菜單 (實際實現)
|
||
console.log('Dictionary menu for:', currentWord.value?.text)
|
||
}
|
||
|
||
// 複製音標功能
|
||
const copyPhonetic = async () => {
|
||
if (currentWord.value?.phonetic) {
|
||
try {
|
||
await navigator.clipboard.writeText(currentWord.value.phonetic)
|
||
$q.notify({
|
||
type: 'positive',
|
||
message: '音標已複製到剪貼板',
|
||
position: 'top-right'
|
||
})
|
||
} catch (error) {
|
||
console.error('複製失敗:', error)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 練習和導航
|
||
const startPractice = () => {
|
||
if (currentWord.value) {
|
||
router.push(`/learning/vocabulary/practice?word=${currentWord.value.id}`)
|
||
}
|
||
}
|
||
|
||
const skipIntroduction = () => {
|
||
// 跳過當前詞彙,進入下一個
|
||
if (currentWordIndex.value < vocabularyWords.value.length - 1) {
|
||
currentWordIndex.value++
|
||
} else {
|
||
router.push('/learning/vocabulary/practice')
|
||
}
|
||
}
|
||
|
||
// 快捷鍵系統 (依據 function-specs)
|
||
const handleKeyboard = (event: KeyboardEvent) => {
|
||
// 避免在輸入框中觸發快捷鍵
|
||
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
||
return
|
||
}
|
||
|
||
switch (event.code) {
|
||
case 'Space':
|
||
event.preventDefault()
|
||
if (event.shiftKey) {
|
||
playSlowAudio()
|
||
} else {
|
||
playAudio()
|
||
}
|
||
break
|
||
case 'Enter':
|
||
event.preventDefault()
|
||
if (event.shiftKey) {
|
||
skipIntroduction()
|
||
} else {
|
||
startPractice()
|
||
}
|
||
break
|
||
case 'KeyD':
|
||
if (event.ctrlKey || event.metaKey) {
|
||
event.preventDefault()
|
||
toggleBookmark()
|
||
}
|
||
break
|
||
case 'KeyN':
|
||
if (event.ctrlKey || event.metaKey) {
|
||
event.preventDefault()
|
||
toggleNotes()
|
||
}
|
||
break
|
||
case 'F1':
|
||
event.preventDefault()
|
||
openDictionary()
|
||
break
|
||
case 'Digit1':
|
||
case 'Digit2':
|
||
case 'Digit3':
|
||
case 'Digit4':
|
||
case 'Digit5':
|
||
const index = parseInt(event.code.slice(-1)) - 1
|
||
if (currentWord.value?.examples[index]) {
|
||
playExampleAudio(currentWord.value.examples[index], index + 1)
|
||
}
|
||
break
|
||
}
|
||
}
|
||
|
||
// 生命週期
|
||
onMounted(() => {
|
||
document.addEventListener('keydown', handleKeyboard)
|
||
// 自動播放第一個詞彙的發音 (依據 function-specs 操作流程)
|
||
if (currentWord.value?.audio_url) {
|
||
setTimeout(() => {
|
||
playAudio()
|
||
}, 1000)
|
||
}
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
document.removeEventListener('keydown', handleKeyboard)
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
// 嚴格對照 vocabulary.html 原型的樣式 + function-specs 多列布局
|
||
.vocabulary-layout {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, var(--bg-primary, #{$background-primary}) 0%, var(--bg-secondary, #{$background-secondary}) 100%);
|
||
display: flex;
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
padding: var(--space-6, #{$space-6});
|
||
overflow-y: auto;
|
||
}
|
||
|
||
// 多列布局容器 (依據 function-specs)
|
||
.vocabulary-content {
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: var(--space-6, #{$space-6});
|
||
|
||
@include respond-to(md) {
|
||
grid-template-columns: 1fr;
|
||
gap: var(--space-4, #{$space-4});
|
||
}
|
||
}
|
||
|
||
// 左側:詞彙資訊主區域
|
||
.vocabulary-main {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
// 右側:相關詞彙和例句面板
|
||
.vocabulary-sidebar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.page-header {
|
||
margin-bottom: var(--space-8, #{$space-8});
|
||
}
|
||
|
||
// 詞性標記 (色彩編碼)
|
||
.part-of-speech {
|
||
display: flex;
|
||
gap: var(--space-2, #{$space-2});
|
||
margin-bottom: var(--space-4, #{$space-4});
|
||
justify-content: center;
|
||
}
|
||
|
||
.pos-tag {
|
||
padding: var(--space-1, #{$space-1}) var(--space-3, #{$space-3});
|
||
border-radius: var(--radius-md, #{$radius-md});
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
font-weight: 600;
|
||
|
||
&.pos-adj {
|
||
background: rgba(156, 39, 176, 0.1);
|
||
color: var(--accent-violet, #{$accent-violet});
|
||
}
|
||
|
||
&.pos-n {
|
||
background: rgba(52, 152, 219, 0.1);
|
||
color: var(--info-cyan, #{$info-cyan});
|
||
}
|
||
|
||
&.pos-v {
|
||
background: rgba(231, 76, 60, 0.1);
|
||
color: var(--error-red, #{$error-red});
|
||
}
|
||
|
||
&.pos-adv {
|
||
background: rgba(243, 156, 18, 0.1);
|
||
color: var(--warning-yellow, #{$warning-yellow});
|
||
}
|
||
}
|
||
|
||
// 詞頻統計 (1-5星評級)
|
||
.frequency-rating {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: var(--space-2, #{$space-2});
|
||
margin-bottom: var(--space-4, #{$space-4});
|
||
}
|
||
|
||
.frequency-label {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
.stars {
|
||
display: flex;
|
||
gap: 2px;
|
||
|
||
.q-icon {
|
||
font-size: 16px;
|
||
color: var(--text-tertiary, #{$text-tertiary});
|
||
transition: color 0.2s ease;
|
||
|
||
&.active {
|
||
color: var(--star-active, #{$star-active});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 音標顯示 (支援點擊複製)
|
||
.vocabulary-phonetic {
|
||
font-size: var(--text-xl, #{$text-xl});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
margin-bottom: var(--space-6, #{$space-6});
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: var(--space-2, #{$space-2});
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
color: var(--primary-teal, #{$primary-teal});
|
||
|
||
.copy-icon {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.copy-icon {
|
||
font-size: 16px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
}
|
||
|
||
// 使用情境說明
|
||
.usage-context {
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
padding: var(--space-4, #{$space-4});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
margin-bottom: var(--space-6, #{$space-6});
|
||
border-left: 4px solid var(--primary-teal, #{$primary-teal});
|
||
|
||
h4 {
|
||
margin: 0 0 var(--space-2, #{$space-2}) 0;
|
||
font-size: var(--text-base, #{$text-base});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
|
||
p {
|
||
margin: 0;
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
// 例句區域
|
||
.examples-section {
|
||
margin-bottom: var(--space-6, #{$space-6});
|
||
|
||
h4 {
|
||
margin: 0 0 var(--space-4, #{$space-4}) 0;
|
||
font-size: var(--text-lg, #{$text-lg});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
.vocabulary-example {
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
padding: var(--space-4, #{$space-4});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
margin-bottom: var(--space-3, #{$space-3});
|
||
position: relative;
|
||
|
||
.example-text {
|
||
font-style: italic;
|
||
color: var(--text-primary, #{$text-primary});
|
||
margin-bottom: var(--space-2, #{$space-2});
|
||
}
|
||
|
||
.example-translation {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
.example-audio-btn {
|
||
position: absolute;
|
||
top: var(--space-2, #{$space-2});
|
||
right: var(--space-2, #{$space-2});
|
||
width: 24px;
|
||
height: 24px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: var(--primary-teal, #{$primary-teal});
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
background: #00b8a0;
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.q-icon {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 控制按鈕增強樣式
|
||
.vocabulary-controls {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: var(--space-3, #{$space-3});
|
||
margin-top: var(--space-8, #{$space-8});
|
||
flex-wrap: wrap;
|
||
|
||
@include respond-to(sm) {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.control-btn {
|
||
padding: var(--space-3, #{$space-3}) var(--space-4, #{$space-4});
|
||
border: 2px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
background: var(--bg-card, #{$card-background});
|
||
color: var(--text-primary, #{$text-primary});
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2, #{$space-2});
|
||
white-space: nowrap;
|
||
|
||
&:hover {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.1);
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
&.primary {
|
||
background: var(--primary-teal, #{$primary-teal});
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
color: white;
|
||
|
||
&:hover {
|
||
background: #00b8a0;
|
||
}
|
||
}
|
||
|
||
&.secondary {
|
||
background: var(--background-secondary, #{$background-secondary});
|
||
border-color: var(--text-tertiary, #{$text-tertiary});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
|
||
&:hover {
|
||
border-color: var(--text-secondary, #{$text-secondary});
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
}
|
||
|
||
&.bookmarked {
|
||
background: var(--warning-yellow, #{$warning-yellow});
|
||
border-color: var(--warning-yellow, #{$warning-yellow});
|
||
color: white;
|
||
|
||
&:hover {
|
||
background: #e67e22;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 學習進度條
|
||
.progress-section {
|
||
margin-top: var(--space-6, #{$space-6});
|
||
|
||
.progress-label {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
margin-bottom: var(--space-2, #{$space-2});
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
// 筆記編輯器區域 (支援Markdown,可摺疊)
|
||
.notes-section {
|
||
background: var(--bg-card, #{$card-background});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-xl, #{$radius-xl});
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
|
||
&.expanded {
|
||
box-shadow: var(--shadow-lg, #{$shadow-lg});
|
||
}
|
||
}
|
||
|
||
.notes-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: var(--space-4, #{$space-4}) var(--space-6, #{$space-6});
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
|
||
&:hover {
|
||
background: rgba(0, 229, 204, 0.05);
|
||
}
|
||
|
||
h4 {
|
||
margin: 0;
|
||
font-size: var(--text-base, #{$text-base});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2, #{$space-2});
|
||
}
|
||
|
||
.q-icon {
|
||
font-size: 20px;
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
}
|
||
|
||
.notes-content {
|
||
padding: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.notes-editor {
|
||
margin-bottom: var(--space-4, #{$space-4});
|
||
}
|
||
|
||
.notes-actions {
|
||
display: flex;
|
||
gap: var(--space-2, #{$space-2});
|
||
margin-bottom: var(--space-4, #{$space-4});
|
||
}
|
||
|
||
.notes-preview {
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
padding: var(--space-4, #{$space-4});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
|
||
h1, h2, h3 {
|
||
margin-top: 0;
|
||
margin-bottom: var(--space-2, #{$space-2});
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
|
||
strong {
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
|
||
em {
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
}
|
||
|
||
// 動畫效果
|
||
.slide-down-enter-active,
|
||
.slide-down-leave-active {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.slide-down-enter-from,
|
||
.slide-down-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
|
||
// 相關詞彙推薦
|
||
.related-words-section {
|
||
background: var(--bg-card, #{$card-background});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-xl, #{$radius-xl});
|
||
padding: var(--space-6, #{$space-6});
|
||
|
||
h4 {
|
||
margin: 0 0 var(--space-4, #{$space-4}) 0;
|
||
font-size: var(--text-lg, #{$text-lg});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
}
|
||
|
||
.related-words-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-3, #{$space-3});
|
||
}
|
||
|
||
.related-word-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: var(--space-3, #{$space-3});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
position: relative;
|
||
|
||
&:hover {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.05);
|
||
transform: translateY(-1px);
|
||
|
||
.new-tab-icon {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.related-word-text {
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
margin-bottom: var(--space-1, #{$space-1});
|
||
}
|
||
|
||
.related-word-def {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
.new-tab-icon {
|
||
position: absolute;
|
||
top: var(--space-2, #{$space-2});
|
||
right: var(--space-2, #{$space-2});
|
||
font-size: 14px;
|
||
color: var(--text-tertiary, #{$text-tertiary});
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
}
|
||
|
||
// 詞彙清單增強
|
||
.vocabulary-list {
|
||
background: var(--bg-card, #{$card-background});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-xl, #{$radius-xl});
|
||
padding: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.vocabulary-items-container {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.vocabulary-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: var(--space-4, #{$space-4});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
margin-bottom: var(--space-3, #{$space-3});
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.05);
|
||
}
|
||
|
||
&.active {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.1);
|
||
box-shadow: 0 0 0 1px rgba(0, 229, 204, 0.2);
|
||
}
|
||
}
|
||
|
||
.header-section {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.header-text h1 {
|
||
font-size: var(--text-3xl, #{$text-3xl});
|
||
font-weight: 700;
|
||
color: var(--text-primary, #{$text-primary});
|
||
margin-bottom: var(--space-2, #{$space-2});
|
||
}
|
||
|
||
.header-text p {
|
||
font-size: var(--text-lg, #{$text-lg});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
.header-stats {
|
||
display: flex;
|
||
gap: var(--space-6, #{$space-6});
|
||
align-items: center;
|
||
}
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: var(--text-2xl, #{$text-2xl});
|
||
font-weight: 700;
|
||
color: var(--primary-teal, #{$primary-teal});
|
||
display: block;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
/* 學習模式選擇 */
|
||
.mode-selector {
|
||
display: flex;
|
||
gap: var(--space-4, #{$space-4});
|
||
margin-bottom: var(--space-8, #{$space-8});
|
||
|
||
@include respond-to(sm) {
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
.mode-card {
|
||
flex: 1;
|
||
background: var(--bg-card, #{$card-background});
|
||
border: 2px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-xl, #{$radius-xl});
|
||
padding: var(--space-6, #{$space-6});
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
|
||
&:hover {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-lg, #{$shadow-lg});
|
||
}
|
||
|
||
&.active {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.1);
|
||
}
|
||
}
|
||
|
||
.mode-icon {
|
||
font-size: var(--text-4xl, #{$text-4xl});
|
||
margin-bottom: var(--space-3, #{$space-3});
|
||
}
|
||
|
||
.mode-title {
|
||
font-size: var(--text-lg, #{$text-lg});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
margin-bottom: var(--space-2, #{$space-2});
|
||
}
|
||
|
||
.mode-description {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
margin-bottom: var(--space-4, #{$space-4});
|
||
}
|
||
|
||
.mode-progress {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-tertiary, #{$text-tertiary});
|
||
}
|
||
|
||
/* 詞彙清單 */
|
||
.vocabulary-list {
|
||
background: var(--bg-card, #{$card-background});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-xl, #{$radius-xl});
|
||
padding: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.list-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--space-6, #{$space-6});
|
||
}
|
||
|
||
.list-title {
|
||
font-size: var(--text-xl, #{$text-xl});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
|
||
.filter-tabs {
|
||
display: flex;
|
||
gap: var(--space-2, #{$space-2});
|
||
}
|
||
|
||
.filter-tab {
|
||
padding: var(--space-2, #{$space-2}) var(--space-4, #{$space-4});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-md, #{$radius-md});
|
||
background: var(--bg-secondary, #{$background-secondary});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active {
|
||
background: var(--primary-teal, #{$primary-teal});
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
color: white;
|
||
}
|
||
}
|
||
|
||
.vocabulary-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: var(--space-4, #{$space-4});
|
||
border: 1px solid var(--divider, #{$divider});
|
||
border-radius: var(--radius-lg, #{$radius-lg});
|
||
margin-bottom: var(--space-3, #{$space-3});
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
border-color: var(--primary-teal, #{$primary-teal});
|
||
background: rgba(0, 229, 204, 0.05);
|
||
}
|
||
}
|
||
|
||
.word-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-4, #{$space-4});
|
||
}
|
||
|
||
.word-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-1, #{$space-1});
|
||
}
|
||
|
||
.word-main {
|
||
font-size: var(--text-lg, #{$text-lg});
|
||
font-weight: 600;
|
||
color: var(--text-primary, #{$text-primary});
|
||
}
|
||
|
||
.word-definition {
|
||
font-size: var(--text-sm, #{$text-sm});
|
||
color: var(--text-secondary, #{$text-secondary});
|
||
}
|
||
|
||
.word-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3, #{$space-3});
|
||
}
|
||
|
||
.mastery-indicator {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--text-tertiary, #{$text-tertiary});
|
||
|
||
&.learned {
|
||
background: var(--success-green, #{$success-green});
|
||
}
|
||
|
||
&.learning {
|
||
background: var(--warning-yellow, #{$warning-yellow});
|
||
}
|
||
}
|
||
|
||
.play-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: var(--primary-teal, #{$primary-teal});
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
background: #00b8a0;
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
</style> |