dramaling-vocab-learning/frontend/app/vocab-designs/page.tsx

634 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import { Navigation } from '@/components/Navigation'
import { ProtectedRoute } from '@/components/ProtectedRoute'
export default function VocabDesignsPage() {
return (
<ProtectedRoute>
<VocabDesignsContent />
</ProtectedRoute>
)
}
function VocabDesignsContent() {
const [selectedDesign, setSelectedDesign] = useState('modern')
const [showPopup, setShowPopup] = useState(false)
// 假詞彙數據
const mockWord = {
word: 'elaborate',
pronunciation: '/ɪˈlæbərət/',
partOfSpeech: 'verb',
translation: '詳細說明',
definition: 'To explain something in more detail; to develop or present a theory, policy, or system in further detail',
synonyms: ['explain', 'detail', 'expand', 'clarify'],
difficultyLevel: 'B2',
isHighValue: true,
example: 'Could you elaborate on your proposal?',
exampleTranslation: '你能詳細說明一下你的提案嗎?'
}
const designs = [
{ id: 'modern', name: '現代玻璃', description: '毛玻璃效果,現代感設計' },
{ id: 'classic', name: '經典卡片', description: '傳統卡片風格,清晰分區' },
{ id: 'minimal', name: '極簡風格', description: '簡潔乾淨,突出重點' },
{ id: 'magazine', name: '雜誌排版', description: '類似雜誌的排版風格' },
{ id: 'mobile', name: '移動應用', description: 'iOS/Android應用風格' },
{ id: 'learning', name: '學習卡片', description: '與學習功能一致的風格' }
]
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* 頁面標題 */}
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-4"></h1>
<p className="text-xl text-gray-600 mb-6">6</p>
{/* 測試詞彙 */}
<div className="inline-block bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<span className="text-gray-600 text-sm"></span>
<button
onClick={() => setShowPopup(true)}
className="ml-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
>
"elaborate"
</button>
</div>
</div>
{/* 版型選擇器 */}
<div className="flex justify-center mb-8">
<div className="bg-white rounded-xl shadow-sm p-2 inline-flex flex-wrap gap-2">
{designs.map((design) => (
<button
key={design.id}
onClick={() => setSelectedDesign(design.id)}
className={`px-4 py-2 rounded-lg transition-all ${
selectedDesign === design.id
? 'bg-primary text-white shadow-md'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100'
}`}
>
{design.name}
</button>
))}
</div>
</div>
{/* 版型預覽 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* 左側:版型說明 */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-4">
{designs.find(d => d.id === selectedDesign)?.name}
</h2>
<p className="text-gray-600 mb-6">
{designs.find(d => d.id === selectedDesign)?.description}
</p>
<div className="space-y-4">
<div>
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<ul className="text-sm text-gray-700 space-y-1">
{getDesignFeatures(selectedDesign).map((feature, idx) => (
<li key={idx} className="flex items-center gap-2">
<span className="text-blue-500"></span>
{feature}
</li>
))}
</ul>
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<p className="text-sm text-gray-700">
{getDesignScenario(selectedDesign)}
</p>
</div>
</div>
</div>
{/* 右側:版型預覽 */}
<div className="bg-gradient-to-br from-gray-100 to-gray-200 rounded-xl p-8 flex items-center justify-center min-h-[600px] relative">
<div className="text-center">
<p className="text-gray-600 mb-4"></p>
<button
onClick={() => setShowPopup(true)}
className="px-6 py-3 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors font-medium shadow-lg"
>
"{mockWord.word}"
</button>
</div>
{/* 模擬背景文字 */}
<div className="absolute inset-4 opacity-10 text-gray-500 text-lg leading-relaxed pointer-events-none">
This is a sample sentence where you can click on any word to see the elaborate definition and detailed explanation.
</div>
</div>
</div>
</div>
{/* 詞彙彈窗 - 根據選擇的設計風格渲染 */}
{showPopup && (
<>
{/* 背景遮罩 */}
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={() => setShowPopup(false)}
/>
{/* 彈窗內容 */}
<div className="fixed inset-0 flex items-center justify-center z-50 p-4">
{renderVocabPopup(selectedDesign, mockWord, () => setShowPopup(false))}
</div>
</>
)}
</div>
)
}
// 渲染不同風格的詞彙彈窗
function renderVocabPopup(design: string, word: any, onClose: () => void) {
const handleSave = () => {
alert(`✅ 已將「${word.word}」保存到詞卡!`)
onClose()
}
switch (design) {
case 'modern':
return <ModernGlassDesign word={word} onClose={onClose} onSave={handleSave} />
case 'classic':
return <ClassicCardDesign word={word} onClose={onClose} onSave={handleSave} />
case 'minimal':
return <MinimalDesign word={word} onClose={onClose} onSave={handleSave} />
case 'magazine':
return <MagazineDesign word={word} onClose={onClose} onSave={handleSave} />
case 'mobile':
return <MobileAppDesign word={word} onClose={onClose} onSave={handleSave} />
case 'learning':
return <LearningCardDesign word={word} onClose={onClose} onSave={handleSave} />
default:
return <ModernGlassDesign word={word} onClose={onClose} onSave={handleSave} />
}
}
// 1. 現代玻璃風格
function ModernGlassDesign({ word, onClose, onSave }: any) {
return (
<div
className="bg-white rounded-2xl shadow-2xl border-0 w-80 backdrop-blur-sm"
style={{
background: 'rgba(255, 255, 255, 0.98)',
backdropFilter: 'blur(12px)',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.8)'
}}
>
<div className="relative p-5 pb-0">
<button
onClick={onClose}
className="absolute top-3 right-3 w-6 h-6 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors flex items-center justify-center text-gray-500"
>
</button>
<div className="pr-8">
<div className="flex items-baseline gap-3 mb-1">
<h3 className="text-2xl font-bold text-gray-900">{word.word}</h3>
{word.isHighValue && <span className="text-yellow-500 text-lg"></span>}
</div>
<div className="flex items-center gap-3 text-gray-600">
<span className="text-sm font-medium">{word.pronunciation}</span>
<span className="text-xs px-2 py-1 rounded-full font-medium bg-blue-100 text-blue-700">
{word.difficultyLevel}
</span>
</div>
</div>
</div>
<div className="px-5 py-4 space-y-4">
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="text-lg font-semibold text-gray-900">{word.translation}</div>
</div>
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="text-sm text-gray-700 leading-relaxed">{word.definition}</div>
</div>
<div>
<div className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"></div>
<div className="flex flex-wrap gap-1">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-blue-50 text-blue-700 px-2 py-1 rounded text-xs font-medium border border-blue-200">
{synonym}
</span>
))}
</div>
</div>
</div>
<div className="p-5 pt-2 border-t border-gray-100">
<button
onClick={onSave}
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-800 transition-all duration-200 transform hover:scale-105 active:scale-95 flex items-center justify-center gap-2 shadow-lg"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
<span className="text-sm font-medium"></span>
</button>
</div>
</div>
)
}
// 2. 經典卡片風格
function ClassicCardDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-96 max-w-md">
<div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-4 rounded-t-lg">
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-bold">{word.word}</h3>
<p className="text-blue-100 text-sm">{word.pronunciation}</p>
</div>
<button
onClick={onClose}
className="text-blue-100 hover:text-white w-8 h-8 rounded-full hover:bg-blue-800 transition-colors flex items-center justify-center"
>
</button>
</div>
</div>
<div className="p-4 space-y-4">
<div className="flex items-center gap-3">
<span className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm font-medium">
{word.partOfSpeech}
</span>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
word.difficultyLevel === 'A1' || word.difficultyLevel === 'A2' ? 'bg-green-100 text-green-700' :
word.difficultyLevel === 'B1' || word.difficultyLevel === 'B2' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{word.difficultyLevel}
</span>
{word.isHighValue && (
<span className="bg-yellow-100 text-yellow-700 px-2 py-1 rounded-full text-xs font-medium">
</span>
)}
</div>
<div className="bg-blue-50 rounded-lg p-3 border-l-4 border-blue-400">
<h4 className="font-semibold text-blue-900 mb-1"></h4>
<p className="text-blue-800 font-medium">{word.translation}</p>
</div>
<div className="bg-gray-50 rounded-lg p-3">
<h4 className="font-semibold text-gray-900 mb-2"></h4>
<p className="text-gray-700 text-sm leading-relaxed">{word.definition}</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 mb-2"></h4>
<div className="flex flex-wrap gap-2">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm">
{synonym}
</span>
))}
</div>
</div>
<button
onClick={onSave}
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
>
<span>💾</span>
<span></span>
</button>
</div>
</div>
)
}
// 3. 極簡風格
function MinimalDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-lg shadow-lg w-72 border border-gray-100">
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold text-gray-900">{word.word}</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 w-5 h-5 flex items-center justify-center"
>
</button>
</div>
<p className="text-sm text-gray-500 mt-1">{word.pronunciation}</p>
</div>
<div className="p-4 space-y-3">
<div>
<span className="text-lg font-medium text-gray-900">{word.translation}</span>
<span className="ml-2 text-xs text-gray-500">({word.partOfSpeech})</span>
</div>
<p className="text-sm text-gray-600 leading-relaxed">{word.definition}</p>
<div className="flex flex-wrap gap-1 pt-2">
{word.synonyms.slice(0, 3).map((synonym: string, idx: number) => (
<span key={idx} className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded">
{synonym}
</span>
))}
</div>
<button
onClick={onSave}
className="w-full mt-4 bg-gray-900 text-white py-2 rounded text-sm font-medium hover:bg-gray-800 transition-colors"
>
</button>
</div>
</div>
)
}
// 4. 雜誌排版風格
function MagazineDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white shadow-xl w-96 max-w-md" style={{ fontFamily: 'Georgia, serif' }}>
<div className="p-6 border-b-2 border-gray-900">
<div className="flex items-start justify-between">
<div>
<h3 className="text-3xl font-bold text-gray-900 mb-1" style={{ fontFamily: 'Georgia, serif' }}>
{word.word}
</h3>
<div className="flex items-center gap-4 text-sm text-gray-600">
<span className="italic">{word.pronunciation}</span>
<span className="font-semibold">{word.partOfSpeech}</span>
<span className="bg-gray-900 text-white px-2 py-1 text-xs">{word.difficultyLevel}</span>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-xl"
>
×
</button>
</div>
</div>
<div className="p-6">
<div className="mb-4">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2 border-b border-gray-200 pb-1">
Translation
</h4>
<p className="text-xl font-semibold text-gray-900" style={{ fontFamily: 'Georgia, serif' }}>
{word.translation}
</p>
</div>
<div className="mb-4">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2">Definition</h4>
<p className="text-sm text-gray-700 leading-relaxed" style={{ textAlign: 'justify' }}>
{word.definition}
</p>
</div>
<div className="mb-6">
<h4 className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2">Synonyms</h4>
<p className="text-sm text-gray-700 italic">
{word.synonyms.join(', ')}
</p>
</div>
<button
onClick={onSave}
className="w-full bg-gray-900 text-white py-3 font-semibold uppercase tracking-wider text-sm hover:bg-gray-800 transition-colors"
>
Add to Collection
</button>
</div>
</div>
)
}
// 5. 移動應用風格
function MobileAppDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-3xl shadow-xl w-80 overflow-hidden">
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold text-sm">
{word.word[0].toUpperCase()}
</div>
<span className="font-medium text-gray-900"></span>
</div>
<button
onClick={onClose}
className="text-blue-500 font-medium text-sm"
>
</button>
</div>
</div>
<div className="p-4">
<div className="text-center mb-4">
<h3 className="text-2xl font-bold text-gray-900 mb-1">{word.word}</h3>
<p className="text-gray-500 text-sm">{word.pronunciation}</p>
<div className="flex items-center justify-center gap-2 mt-2">
<span className="bg-gray-100 text-gray-700 px-2 py-1 rounded-full text-xs">
{word.partOfSpeech}
</span>
<span className={`px-2 py-1 rounded-full text-xs ${
word.difficultyLevel === 'A1' || word.difficultyLevel === 'A2' ? 'bg-green-100 text-green-700' :
word.difficultyLevel === 'B1' || word.difficultyLevel === 'B2' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700'
}`}>
{word.difficultyLevel}
</span>
</div>
</div>
<div className="space-y-3">
<div className="flex items-start gap-3 py-2">
<div className="w-8 text-center">🌏</div>
<div className="flex-1">
<div className="font-medium text-gray-900">{word.translation}</div>
<div className="text-xs text-gray-500"></div>
</div>
</div>
<div className="flex items-start gap-3 py-2 border-t border-gray-100">
<div className="w-8 text-center">📝</div>
<div className="flex-1">
<div className="text-sm text-gray-700 leading-relaxed">{word.definition}</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
</div>
<div className="flex items-start gap-3 py-2 border-t border-gray-100">
<div className="w-8 text-center">🔗</div>
<div className="flex-1">
<div className="flex flex-wrap gap-1">
{word.synonyms.slice(0, 3).map((synonym: string, idx: number) => (
<span key={idx} className="bg-gray-100 text-gray-700 px-2 py-1 rounded-full text-xs">
{synonym}
</span>
))}
</div>
<div className="text-xs text-gray-500 mt-1"></div>
</div>
</div>
</div>
<button
onClick={onSave}
className="w-full mt-6 bg-blue-500 text-white py-3 rounded-xl font-medium text-base hover:bg-blue-600 transition-colors active:bg-blue-700"
>
</button>
</div>
</div>
)
}
// 6. 學習卡片風格
function LearningCardDesign({ word, onClose, onSave }: any) {
return (
<div className="bg-white rounded-xl shadow-lg w-96 max-w-md overflow-hidden">
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold">
{word.word[0].toUpperCase()}
</div>
<div>
<h3 className="text-lg font-bold text-gray-900">{word.word}</h3>
<p className="text-sm text-gray-600">{word.partOfSpeech}</p>
</div>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
</button>
</div>
<div className="flex items-center gap-3">
<span className="text-sm font-medium text-gray-700">{word.pronunciation}</span>
<button className="text-blue-600 hover:text-blue-700 p-1 rounded-full hover:bg-blue-50 transition-colors">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
</button>
</div>
</div>
<div className="p-5 space-y-4">
<div className="bg-green-50 rounded-lg p-4">
<h4 className="font-semibold text-green-900 mb-2 text-left"></h4>
<p className="text-green-800 font-medium text-left">{word.translation}</p>
</div>
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="font-semibold text-gray-900 mb-2 text-left"></h4>
<p className="text-gray-700 text-sm text-left leading-relaxed">{word.definition}</p>
</div>
<div className="bg-blue-50 rounded-lg p-4">
<h4 className="font-semibold text-blue-900 mb-2 text-left"></h4>
<div className="flex flex-wrap gap-2">
{word.synonyms.map((synonym: string, idx: number) => (
<span key={idx} className="bg-white text-blue-700 px-3 py-1 rounded-full text-sm border border-blue-200">
{synonym}
</span>
))}
</div>
</div>
<button
onClick={onSave}
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
>
<span>💾</span>
<span></span>
</button>
</div>
</div>
)
}
// 輔助函數
function getDesignFeatures(design: string): string[] {
const features = {
modern: [
'毛玻璃背景效果',
'大尺寸陰影和圓角',
'漸層按鈕設計',
'微互動動畫',
'現代配色方案'
],
classic: [
'藍色漸層標題欄',
'清晰的區塊分隔',
'傳統卡片佈局',
'顏色編碼標籤',
'穩重的設計風格'
],
minimal: [
'極簡配色方案',
'去除多餘裝飾',
'重點信息突出',
'輕量化設計',
'快速瀏覽體驗'
],
magazine: [
'雜誌式字體',
'專業排版設計',
'大標題小說明',
'黑白主色調',
'閱讀導向佈局'
],
mobile: [
'iOS/Android風格',
'圓角和圖標設計',
'列表式信息展示',
'觸控友好設計',
'移動端優化'
],
learning: [
'學習功能一致性',
'彩色區塊設計',
'教育導向佈局',
'清晰的信息分類',
'學習體驗優化'
]
}
return features[design as keyof typeof features] || []
}
function getDesignScenario(design: string): string {
const scenarios = {
modern: '適合追求現代感和科技感的用戶,特別是年輕用戶群體。設計前衛,視覺效果佳。',
classic: '適合喜歡傳統界面的用戶,信息展示清晰,功能分區明確,適合學術或商務場景。',
minimal: '適合追求效率的用戶,減少視覺干擾,快速獲取核心信息,適合頻繁使用的場景。',
magazine: '適合喜歡閱讀體驗的用戶,類似字典或雜誌的專業排版,適合深度學習。',
mobile: '適合手機用戶,觸控友好,符合移動端應用的使用習慣,適合隨時隨地學習。',
learning: '與現有學習功能保持一致,用戶體驗連貫,適合在學習流程中使用。'
}
return scenarios[design as keyof typeof scenarios] || ''
}