forked from Tadrisino/MathApp_frontend
Smoother tab switch (no dark flash), full welcome sentence, chapter banner sticky and chip centered, floating math glyphs in headers, quiz reaction emojis
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,15 +74,18 @@ function Screens({ onSignedOut }: Pick<Props, 'onSignedOut'>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex-1 overflow-hidden">
|
<div className="relative flex-1 overflow-hidden bg-page">
|
||||||
<AnimatePresence mode="popLayout" initial={false}>
|
<AnimatePresence mode="popLayout" initial={false}>
|
||||||
<motion.div
|
<motion.div
|
||||||
key={JSON.stringify(route)}
|
key={JSON.stringify(route)}
|
||||||
className="absolute inset-0 flex flex-col"
|
className="absolute inset-0 flex flex-col bg-page"
|
||||||
initial={{ opacity: 0, x: -18 }}
|
initial={{ opacity: 0, x: -14 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
exit={{ opacity: 0, x: 18 }}
|
// صفحهی قبلی نمیپرد و محو نمیشود، زیرِ صفحهی تازه میماند تا کامل بیاید. با محوشدنِ همزمانِ
|
||||||
transition={{ duration: 0.18 }}
|
// هر دو، پسزمینهی تیرهی body از وسط دیده میشد و تعویضِ تب «گلیچ» میزد (از روی کد؛ با
|
||||||
|
// فیلمِ دستگاهِ واقعی سنجیده نشده).
|
||||||
|
exit={{ opacity: 1, x: 0, transition: { duration: 0.16 } }}
|
||||||
|
transition={{ duration: 0.16, ease: 'easeOut' }}
|
||||||
>
|
>
|
||||||
<Suspense fallback={<LoadingState />}>
|
<Suspense fallback={<LoadingState />}>
|
||||||
<RouteView route={route} onSignedOut={onSignedOut} />
|
<RouteView route={route} onSignedOut={onSignedOut} />
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ export function ScreenHeader({
|
|||||||
>
|
>
|
||||||
<div className="pointer-events-none absolute -left-12 -top-12 h-40 w-40 rounded-full bg-white/10" />
|
<div className="pointer-events-none absolute -left-12 -top-12 h-40 w-40 rounded-full bg-white/10" />
|
||||||
<div className="pointer-events-none absolute -right-10 top-10 h-28 w-28 rounded-full bg-white/10" />
|
<div className="pointer-events-none absolute -right-10 top-10 h-28 w-28 rounded-full bg-white/10" />
|
||||||
|
{/* نمادهای ریاضی که آرام شناورند؛ فقط transform/opacity و بیتصویر، پس ارزاناند. */}
|
||||||
|
<span aria-hidden className="glyph pointer-events-none absolute left-[42%] top-5 text-3xl font-black text-white/15" style={{ animationDelay: '0s' }}>π</span>
|
||||||
|
<span aria-hidden className="glyph pointer-events-none absolute right-[34%] bottom-3 text-2xl font-black text-white/15" style={{ animationDelay: '-2s' }}>∑</span>
|
||||||
|
<span aria-hidden className="glyph pointer-events-none absolute left-[26%] bottom-2 text-2xl font-black text-white/15" style={{ animationDelay: '-4s' }}>√</span>
|
||||||
<div className="relative z-10 pt-6">
|
<div className="relative z-10 pt-6">
|
||||||
{back && (typeof back === 'function' || nav.canGoBack) && (
|
{back && (typeof back === 'function' || nav.canGoBack) && (
|
||||||
<IconButton label="بازگشت" onClick={onBack} className="mb-3 bg-white/15">
|
<IconButton label="بازگشت" onClick={onBack} className="mb-3 bg-white/15">
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function HomeScreen() {
|
|||||||
: !home.units.length
|
: !home.units.length
|
||||||
? 'هنوز درسی برای این پایه ثبت نشده.'
|
? 'هنوز درسی برای این پایه ثبت نشده.'
|
||||||
: cont
|
: cont
|
||||||
? <>سلام {name}! 👋 نوبتِ جزیرهی <b>«<MathText text={cont.node.title} />»</b>ـه. دکمهی <b>شروع</b> رو بزن.</>
|
? <>سلام {name}! 👋 وقتشه جزیرهی <b>«<MathText text={cont.node.title} />»</b> رو شروع کنی؛ دکمهی <b>شروع</b> رو بزن.</>
|
||||||
: <>آفرین {name}! همهی جزیرههای این پایه رو تموم کردی 🎉</>;
|
: <>آفرین {name}! همهی جزیرههای این پایه رو تموم کردی 🎉</>;
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
@@ -105,13 +105,13 @@ export function HomeScreen() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => nav.push({ name: 'unit', unitId: unit.id })}
|
onClick={() => nav.push({ name: 'unit', unitId: unit.id })}
|
||||||
className="flex w-full items-center gap-3 rounded-2xl px-4 py-3 text-right text-white active:translate-y-0.5"
|
className="sticky top-2 z-10 flex w-full items-center gap-3 rounded-2xl px-4 py-3 text-right text-white active:translate-y-0.5"
|
||||||
style={{ backgroundColor: onWhite(color), boxShadow: `0 4px 0 ${shade(onWhite(color), -0.25)}` }}
|
style={{ backgroundColor: onWhite(color), boxShadow: `0 4px 0 ${shade(onWhite(color), -0.25)}` }}
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="text-[11px] font-bold text-white/80">
|
<div className="text-[11px] font-bold text-white/80">
|
||||||
فصلِ {toFa(i + 1)}<Sep />{toFa(p.done)} از {toFa(p.total)} جزیره
|
فصلِ {toFa(i + 1)}<Sep />{toFa(p.done)} از {toFa(p.total)} جزیره
|
||||||
{isCurrentUnit && <span className="mr-1.5 rounded-full bg-white/25 px-2 py-0.5">الان اینجایی</span>}
|
{isCurrentUnit && <span className="mr-1.5 inline-flex h-5 items-center rounded-full bg-white/25 px-2 align-middle leading-none">الان اینجایی</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 truncate text-[15px] font-black">{unit.title.replace(/^فصل\s*\S+\s*[::]\s*/, '')}</div>
|
<div className="mt-0.5 truncate text-[15px] font-black">{unit.title.replace(/^فصل\s*\S+\s*[::]\s*/, '')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
|
|||||||
const bubble = !answer
|
const bubble = !answer
|
||||||
? 'جوابِ درست رو انتخاب کن!'
|
? 'جوابِ درست رو انتخاب کن!'
|
||||||
: answer.correct
|
: answer.correct
|
||||||
? 'آفرین! درست بود ⚡'
|
? 'آفرین! درست بود'
|
||||||
: outOfHearts
|
: outOfHearts
|
||||||
? 'قلبهات تموم شد؛ یه کم استراحت کنیم.'
|
? 'قلبهات تموم شد؛ یه کم استراحت کنیم.'
|
||||||
: 'اشکالی نداره، جوابِ درست رو ببین.';
|
: 'اشکالی نداره، جوابِ درست رو ببین.';
|
||||||
@@ -282,6 +282,7 @@ export function QuizScreen({ lessonId, nodeId }: { lessonId?: number; nodeId?: s
|
|||||||
!answer ? 'border-line bg-white text-indigo' : answer.correct ? 'border-green-200 bg-green-50 text-green-700' : 'border-purple-200 bg-purple-50 text-[#5B3FA0]'
|
!answer ? 'border-line bg-white text-indigo' : answer.correct ? 'border-green-200 bg-green-50 text-green-700' : 'border-purple-200 bg-purple-50 text-[#5B3FA0]'
|
||||||
}`}>
|
}`}>
|
||||||
{bubble}
|
{bubble}
|
||||||
|
{answer && <Reaction key={question.id} correct={answer.correct} seed={index} />}
|
||||||
{answer?.correct && answer.xpEarned > 0 && (
|
{answer?.correct && answer.xpEarned > 0 && (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={question.id}
|
key={question.id}
|
||||||
@@ -518,3 +519,21 @@ function ReportSheet({ open, questionId, onClose }: { open: boolean; questionId:
|
|||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** شکلکِ کوچکِ بعد از جواب؛ هر سوال یکی از مجموعه، با یک تکانِ کوتاه. فقط ایموجی و transform. */
|
||||||
|
const WIN = ['🧠', '💪', '🎯', '⚡', '🤩'];
|
||||||
|
const MISS = ['🤔', '💡', '📐'];
|
||||||
|
function Reaction({ correct, seed }: { correct: boolean; seed: number }) {
|
||||||
|
const set = correct ? WIN : MISS;
|
||||||
|
return (
|
||||||
|
<motion.span
|
||||||
|
aria-hidden
|
||||||
|
className="mr-1.5 inline-block"
|
||||||
|
initial={{ scale: 0.4, rotate: 0 }}
|
||||||
|
animate={{ scale: 1, rotate: [0, -14, 14, -8, 8, 0] }}
|
||||||
|
transition={{ duration: 0.6, ease: 'easeOut' }}
|
||||||
|
>
|
||||||
|
{set[seed % set.length]}
|
||||||
|
</motion.span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,3 +93,13 @@
|
|||||||
@utility pb-safe {
|
@utility pb-safe {
|
||||||
padding-bottom: max(env(safe-area-inset-bottom), 0px);
|
padding-bottom: max(env(safe-area-inset-bottom), 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* نمادهای شناورِ سربرگ (ui.tsx). فقط transform، پس روی GPU میماند. */
|
||||||
|
@keyframes glyph-float {
|
||||||
|
0%, 100% { transform: translateY(0) rotate(-6deg); }
|
||||||
|
50% { transform: translateY(-7px) rotate(6deg); }
|
||||||
|
}
|
||||||
|
.glyph {
|
||||||
|
animation: glyph-float 6s ease-in-out infinite;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user