feat(lessons): add lessons from client db

This commit is contained in:
shafin-r
2026-03-01 20:24:14 +06:00
parent 2eaf77e13c
commit 2a00c44157
152 changed files with 74587 additions and 222 deletions

View File

@ -0,0 +1,329 @@
import React, { useRef, useState, useEffect } from "react";
import { ArrowDown, Check, BookOpen, AlertTriangle, Zap } from "lucide-react";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
INFERENCES_EASY,
INFERENCES_MEDIUM,
} from "../../../data/rw/inferences";
interface LessonProps {
onFinish?: () => void;
}
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question:
"What can be inferred from this passage about the long-term effects of the policy?",
passage: [
"When the city introduced congestion pricing in 2019, many business owners predicted economic disaster.",
"Three years later, traffic in the city center had declined by 28%, and air quality had measurably improved.",
"Revenue from the pricing scheme was reinvested in public transit, increasing bus and metro frequency by 40%.",
"Business revenues in the city center rose by an average of 12% over the same period, contradicting earlier fears.",
"Several other major cities are now closely studying the program as a potential model.",
],
evidenceIndex: 3,
explanation:
"Sentence 4 most strongly supports the inference that the policy had positive long-term effects on the local economy — directly contradicting predictions of economic harm. This is a valid inference because it follows necessarily from the evidence. Sentence 5 supports the inference that the policy was considered successful, but sentence 4 specifically addresses the economic outcome.",
},
{
question:
"What does the passage imply about the relationship between diet and cognitive decline?",
passage: [
"Alzheimer's disease affects more than 55 million people worldwide.",
"In recent years, researchers have shifted focus from genetic factors alone to lifestyle factors, including diet.",
"Several large-scale studies have found that individuals who follow Mediterranean-style diets — rich in vegetables, fish, and olive oil — show slower rates of cognitive decline.",
"However, researchers caution that correlation does not establish causation, and no single food has been proven to prevent Alzheimer's.",
"Still, the evidence is strong enough that many neurologists now discuss dietary patterns with patients at risk.",
],
evidenceIndex: 2,
explanation:
'Sentence 3 most directly supports the implication: Mediterranean diets are associated with slower cognitive decline. This is an inference the passage clearly supports. Sentence 4 is a caution about causation — it limits the inference, which is exactly why "diet prevents Alzheimer\'s" (too strong) would be wrong.',
},
{
question:
"What can be inferred about the scientist's attitude toward the technology?",
passage: [
"Dr. Reyes has spent the last decade studying CRISPR applications in agriculture.",
"In her 2023 report, she called the technology 'one of the most significant breakthroughs in food science in the last fifty years.'",
"She was careful, however, to note that large-scale deployment would require extensive safety testing over multiple growing seasons.",
"She also advocated for transparent public communication about how modified crops differ from conventional ones.",
"Despite her caution, her lab has continued to accelerate its own research timeline.",
],
evidenceIndex: 1,
explanation:
"Sentence 2 most directly reveals the scientist's attitude: she views CRISPR as one of the most significant breakthroughs in fifty years — clearly positive. The word \"careful\" in sentence 3 adds nuance but doesn't change her fundamental enthusiasm. An inference about her attitude should be grounded in her own words in sentence 2.",
},
];
const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
const [activeSection, setActiveSection] = useState(0);
const sectionsRef = useRef<(HTMLElement | null)[]>([]);
useEffect(() => {
const observers: IntersectionObserver[] = [];
sectionsRef.current.forEach((el, idx) => {
if (!el) return;
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setActiveSection(idx);
},
{ threshold: 0.3 },
);
obs.observe(el);
observers.push(obs);
});
return () => observers.forEach((o) => o.disconnect());
}, []);
const scrollToSection = (i: number) => {
setActiveSection(i);
sectionsRef.current[i]?.scrollIntoView({ behavior: "smooth" });
};
const SectionMarker = ({
index,
title,
icon: Icon,
}: {
index: number;
title: string;
icon: React.ElementType;
}) => {
const isActive = activeSection === index;
const isPast = activeSection > index;
return (
<button
onClick={() => scrollToSection(index)}
className={`flex items-center gap-3 p-3 w-full rounded-lg text-left transition-all ${isActive ? "bg-teal-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 ${isActive ? "bg-teal-600 text-white" : isPast ? "bg-teal-400 text-white" : "bg-slate-200 text-slate-500"}`}
>
{isPast ? (
<Check className="w-4 h-4" />
) : (
<Icon className="w-4 h-4" />
)}
</div>
<p
className={`text-sm font-bold ${isActive ? "text-teal-900" : "text-slate-600"}`}
>
{title}
</p>
</button>
);
};
return (
<div className="flex flex-col lg:flex-row min-h-screen">
<aside className="w-full lg:w-64 lg:fixed lg:top-14 lg:bottom-0 lg:overflow-y-auto p-4 border-r border-slate-200 bg-slate-50 z-0 hidden lg:block">
<nav className="space-y-2 pt-6">
<SectionMarker
index={0}
title="Concept & Annotation"
icon={BookOpen}
/>
<SectionMarker
index={1}
title="Evidence Hunter"
icon={AlertTriangle}
/>
<SectionMarker index={2} title="Practice Quiz" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto">
{/* Section 0: Concept & Annotation */}
<section
ref={(el) => {
sectionsRef.current[0] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24 pt-20 lg:pt-0"
>
<div className="inline-flex items-center gap-2 bg-teal-100 text-teal-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4">
Information & Ideas
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Inferences
</h2>
<p className="text-lg text-slate-500 mb-8">
A valid inference is not stated but is strongly supported. It never
exceeds what the text supports and never uses extreme language.
</p>
{/* Rule grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
{[
{
num: "1",
title: "Inference = Logical Extension",
body: "An inference is not stated directly. It's a conclusion that must logically follow from what the text says.",
},
{
num: "2",
title: "Stay Close to the Text",
body: "The SAT rewards inferences that are a small, necessary step from the evidence. Avoid dramatic leaps.",
},
{
num: "3",
title: "Supported, Not Proven",
body: "A valid inference is supported by the text but not explicitly stated. It must be consistent with ALL of the passage, not just one line.",
},
{
num: "4",
title: "Eliminate Extreme Language",
body: "Inferences with 'always,' 'never,' 'all,' 'none,' 'impossible' are almost always wrong — the passage rarely proves absolutes.",
},
].map((rule) => (
<div
key={rule.num}
className="rounded-2xl border border-teal-200 bg-teal-50 p-5"
>
<div className="flex items-center gap-2 mb-2">
<span className="w-7 h-7 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{rule.num}
</span>
<p className="text-sm font-bold text-teal-900">
{rule.title}
</p>
</div>
<p className="text-sm text-slate-700 leading-relaxed">
{rule.body}
</p>
</div>
))}
</div>
{/* Static annotation visual */}
<h3 className="text-lg font-bold text-slate-800 mb-3">
Valid vs. Invalid Inference Annotated
</h3>
<div className="space-y-3 mb-8">
<div className="rounded-xl bg-teal-100 border border-teal-200 p-4">
<p className="text-xs font-bold text-teal-700 uppercase tracking-wider mb-1">
Stated in the text
</p>
<p className="text-sm text-slate-800">
"The researcher found that sleep-deprived students scored 15%
lower on memory tests."
</p>
</div>
<div className="rounded-xl bg-green-100 border border-green-200 p-4">
<p className="text-xs font-bold text-green-700 uppercase tracking-wider mb-1">
Valid inference
</p>
<p className="text-sm text-slate-800">
Sleep deprivation negatively affects memory performance.
</p>
</div>
<div className="rounded-xl bg-orange-100 border border-orange-200 p-4">
<p className="text-xs font-bold text-orange-700 uppercase tracking-wider mb-1">
Invalid inference
</p>
<p className="text-sm text-slate-800">
"Sleep is the most important factor in academic performance."
Too broad, not proven by one study.
</p>
</div>
</div>
{/* Trap callout */}
<div className="rounded-2xl bg-red-50 border border-red-200 p-5 mb-8">
<p className="text-sm font-bold text-red-800 mb-2">
Inference Trap
</p>
<p className="text-sm text-slate-700 leading-relaxed">
A choice that is plausible in real life but goes BEYOND what the
passage can actually support. Always ask: "Can I prove this using
only what the passage says?"
</p>
</div>
{/* Golden rule */}
<div className="rounded-2xl bg-teal-900 p-6 mb-8">
<p className="text-xs font-bold text-teal-300 uppercase tracking-wider mb-2">
Golden Rule
</p>
<p className="text-white font-semibold leading-relaxed">
Inferences are the smallest logical step the text allows. If the
inference requires outside knowledge or an additional assumption,
it's wrong.
</p>
</div>
<button
onClick={() => scrollToSection(1)}
className="mt-4 group flex items-center text-teal-600 font-bold hover:text-teal-800 transition-colors"
>
Next: Evidence Hunter{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 1: Evidence Hunter */}
<section
ref={(el) => {
sectionsRef.current[1] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<div className="inline-flex items-center gap-2 bg-teal-100 text-teal-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4">
Interactive Practice
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Evidence Hunter
</h2>
<p className="text-lg text-slate-500 mb-8">
For each passage, click the sentence that most strongly supports the
inference asked. Think: which sentence does the most work for this
conclusion?
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="teal"
/>
<button
onClick={() => scrollToSection(2)}
className="mt-12 group flex items-center text-teal-600 font-bold hover:text-teal-800 transition-colors"
>
Next: Practice Quiz{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 2: Practice Quiz */}
<section
ref={(el) => {
sectionsRef.current[2] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Quiz
</h2>
{INFERENCES_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="teal" />
))}
{INFERENCES_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="teal" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-teal-900 text-white font-bold rounded-full hover:bg-teal-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWInferencesLesson;