Sabrina一對一精緻英文課程

Sabrina 英文小教室 https://unpkg.com/react@18/umd/react.production.min.js https://unpkg.com/react-dom@18/umd/react-dom.production.min.js https://unpkg.com/@babel/standalone/babel.min.js https://cdn.tailwindcss.com https://unpkg.com/framer-motion@10.16.4/dist/framer-motion.js @import url(‘https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Inter:wght@300;400;500;600;700&display=swap’); #sabrina-app-root { –sabrina-sage: #84A59D; –sabrina-rose: #C9ADA7; –sabrina-purple: #9A8C98; –sabrina-navy: #4A4E69; –sabrina-cream: #F2E9E4; –sabrina-dark: #22223B; } body { margin: 0; font-family: ‘Inter’, sans-serif; background-color: transparent; } .font-serif { font-family: ‘Playfair Display’, serif; } .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } .fade-in { animation: fadeIn 0.3s ease-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* 單字卡翻轉效果 */ .card-container { perspective: 1000px; } .card-inner { position: relative; width: 100%; height: 100%; transition: transform 0.6s; transform-style: preserve-3d; cursor: pointer; } .card-inner.is-flipped { transform: rotateY(180deg); } .card-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 2.5rem; } .card-back { transform: rotateY(180deg); }
const { useState, useEffect, useMemo } = React; const { motion, AnimatePresence } = window.Motion; // — SVG 圖示元件 (穩定性最高) — const Icon = ({ name, size = 20, className = “" }) => { const icons = { Calendar: , BookOpen: , BookMarked: , Mic2: , User: , Coffee: , Brain: , Sparkle: , Shield: , Heart: , Trash2: , Alert: , X: }; return icons[name] || null; }; const App = () => { const [activeTab, setActiveTab] = useState(‘calendar’); const [role, setRole] = useState(‘student’); const [activeWeek, setActiveWeek] = useState(0); const [selectedSlot, setSelectedSlot] = useState(null); const [isBookingModalOpen, setIsBookingModalOpen] = useState(false); const [myBookings, setMyBookings] = useState([]); const [cancelTarget, setCancelTarget] = useState(null); const [aiContent, setAiContent] = useState(null); const [isFlipped, setIsFlipped] = useState(false); const [isRecording, setIsRecording] = useState(false); // 初始化模擬數據 useEffect(() => { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const nextWeek = new Date(); nextWeek.setDate(nextWeek.getDate() + 5); setMyBookings([ { id: 101, date: tomorrow.toDateString(), displayDate: tomorrow.toLocaleDateString(‘zh-TW’, { month: ‘short’, day: ‘numeric’ }), slot: “09:00 – 10:30″, fullDate: tomorrow.getTime() }, { id: 102, date: nextWeek.toDateString(), displayDate: nextWeek.toLocaleDateString(‘zh-TW’, { month: ‘short’, day: ‘numeric’ }), slot: “13:30 – 15:00″, fullDate: nextWeek.getTime() } ]); }, []); const monthDays = useMemo(() => { const weeks = []; for (let w = 0; w < 4; w++) { const days = []; for (let i = 0; i { if (selectedSlot) { setMyBookings([…myBookings, { id: Date.now(), date: selectedSlot.day.toDateString(), displayDate: selectedSlot.day.toLocaleDateString(‘zh-TW’, { month: ‘short’, day: ‘numeric’ }), slot: selectedSlot.slot, fullDate: selectedSlot.day.getTime() }]); setIsBookingModalOpen(false); setSelectedSlot(null); } }; const isSlotBooked = (day, slot) => myBookings.some(b => b.date === day.toDateString() && b.slot === slot); const checkCanModify = (bookingTimestamp) => { const now = Date.now(); const diff = bookingTimestamp – now; return diff > (72 * 60 * 60 * 1000); // 72H 邏輯 }; return (
{/* Header */}

Sabrina English {role === ‘teacher’ && • 教師模式}

{activeTab}

{/* Main Content Area */}
{/* 預約系統 (Calendar) */} {activeTab === ‘calendar’ && (
{[1, 2, 3, 4].map((w, idx) => ( ))}
{monthDays[activeWeek].map((day, i) => (
{day.getDate()}
{day.toLocaleDateString(‘zh-TW’, { weekday: ‘long’ })}
{timeSlots.slice(0, 4).map(slot => { const booked = isSlotBooked(day, slot); return ( ); })}
))}
)} {/* 教材與新聞 (Materials) */} {activeTab === ‘materials’ && (

每週新聞時事導讀

{[“AI 如何重塑職場生態", “全球永續時尚趨勢"].map(title => (

{title}

{aiContent === title && (

✨ Sabrina’s AI Guide

這篇文章探討了 AI 科技如何改變未來五年的職場互動與技能需求… (解析生成中)
)}
))}
)} {/* 單字卡 (Flashcards) */} {activeTab === ‘flashcards’ && (
setIsFlipped(!isFlipped)}>
Vocabulary

Aesthetic

Tap to flip

“Concerned with beauty or the appreciation of beauty."

“The app has a very Morandi aesthetic."

)} {/* 口說挑戰 (Speaking) */} {activeTab === ‘speaking’ && (

每週挑戰:職涯規劃

Describe your career goals for the next 5 years. Why is English important for your journey?

{isRecording ? ‘Recording…’ : ‘Tap to Start’}

)} {/* 個人設定與 72H 取消政策 (Profile) */} {activeTab === ‘profile’ && (
S

Sabrina 學生

{role === ‘student’ && (

我的預約紀錄 72H Policy

{myBookings.length === 0 ? ( 尚無預約紀錄 ) : ( myBookings.map(b => { const canModify = checkCanModify(b.fullDate); return (

{b.displayDate}

{b.slot}

已確認
{canModify ? ( ) : (
72 小時內不可取消、更改
)}
); }) )}
)}
)}
{/* Booking Confirmation Modal */} {isBookingModalOpen && (

確認預約嗎?

{selectedSlot?.day.toLocaleDateString(‘zh-TW’, { month: ‘short’, day: ‘numeric’ })} 的 {selectedSlot?.slot}

)} {/* Cancel Confirmation Modal */} {cancelTarget && (

確定取消預約?

您即將取消 {cancelTarget.displayDate} {cancelTarget.slot} 的課程。

)} {/* Bottom Nav Bar */} {[ { id: ‘calendar’, icon: ‘Calendar’, label: role === ‘teacher’ ? ‘課表’ : ‘預約’ }, { id: ‘materials’, icon: ‘BookOpen’, label: ‘教材’ }, { id: ‘flashcards’, icon: ‘BookMarked’, label: ‘單字’ }, { id: ‘speaking’, icon: ‘Mic2’, label: ‘口說’ }, { id: ‘profile’, icon: ‘User’, label: ‘個人’ } ].map(tab => ( ))}
); }; const root = ReactDOM.createRoot(document.getElementById(‘sabrina-app-root’)); root.render();