Files
edbridge-scholars/src/pages/student/lessons/ProportionalLesson.tsx
2026-03-01 20:24:14 +06:00

481 lines
20 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.

import React, { useRef, useState, useEffect } from "react";
import {
ArrowDown,
Check,
BookOpen,
Scale,
Calculator,
ArrowLeftRight,
Percent,
} from "lucide-react";
import RatioVisualizerWidget from "../../../components/lessons/RatioVisualizerWidget";
import UnitConversionWidget from "../../../components/lessons/UnitConversionWidget";
import PercentChangeWidget from "../../../components/lessons/PercentChangeWidget";
import MultiStepPercentWidget from "../../../components/lessons/MultiStepPercentWidget";
import Quiz from "../../../components/lessons/Quiz";
import {
PROPORTIONAL_QUIZ_DATA,
PERCENTAGES_ADV_QUIZ,
PERCENTAGES_QUIZ_DATA,
} from "../../../utils/constants";
import { Frac } from "../../../components/Math";
interface LessonProps {
onFinish?: () => void;
}
const ProportionalLesson: React.FC<LessonProps> = ({ onFinish }) => {
const [activeSection, setActiveSection] = useState(0);
const sectionsRef = useRef<(HTMLElement | null)[]>([]);
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const index = sectionsRef.current.indexOf(
entry.target as HTMLElement,
);
if (index !== -1) {
setActiveSection(index);
}
}
});
},
{ rootMargin: "-20% 0px -60% 0px" },
);
sectionsRef.current.forEach((section) => {
if (section) observer.observe(section);
});
return () => observer.disconnect();
}, []);
const SectionMarker = ({
index,
title,
icon: Icon,
}: {
index: number;
title: string;
icon: any;
}) => {
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 transition-all ${isActive ? "bg-white shadow-md border border-amber-100" : "hover:bg-slate-100"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 ${isActive ? "bg-amber-600 text-white" : isPast ? "bg-amber-400 text-white" : "bg-slate-200 text-slate-500"}`}
>
{isPast ? (
<Check className="w-4 h-4" />
) : (
<Icon className="w-4 h-4" />
)}
</div>
<div className="text-left">
<p
className={`text-sm font-bold ${isActive ? "text-amber-900" : "text-slate-600"}`}
>
{title}
</p>
</div>
</button>
);
};
const allQuizzes = [
...PROPORTIONAL_QUIZ_DATA,
...PERCENTAGES_ADV_QUIZ,
...PERCENTAGES_QUIZ_DATA,
];
return (
<div className="flex flex-col lg:flex-row min-h-screen">
<aside className="w-full lg:w-64 lg:fixed lg:top-20 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">
<SectionMarker index={0} title="Ratios" icon={Scale} />
<SectionMarker
index={1}
title="Unit Conversions"
icon={ArrowLeftRight}
/>
<SectionMarker index={2} title="Percent Changes" icon={Percent} />
<SectionMarker
index={3}
title="Multi-Step & Interest"
icon={Calculator}
/>
<SectionMarker index={4} title="Practice" icon={BookOpen} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto">
{/* Section 1: Ratios */}
<section
ref={(el) => {
sectionsRef.current[0] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24 pt-20 lg:pt-0"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Ratios & Proportions
</h2>
<div className="prose prose-slate text-lg text-slate-600 mb-8">
<p>
A <strong>Ratio</strong> compares two quantities. A critical SAT
skill is distinguishing between <strong>Part-to-Part</strong> and{" "}
<strong>Part-to-Whole</strong> ratios, and knowing which one a
question is asking for.
</p>
<div className="grid md:grid-cols-2 gap-4 mt-4">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">Part-to-Part</p>
<p className="text-sm text-slate-700 mb-2">
Compares one part of a whole to another part. Neither number
is the total.
</p>
<div className="font-mono text-center bg-white py-1 rounded text-amber-700 text-sm">
Boys : Girls = 3 : 2
</div>
<p className="text-xs text-slate-500 mt-2">
Means: for every 3 boys there are 2 girls. Total parts = 5.
</p>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">Part-to-Whole</p>
<p className="text-sm text-slate-700 mb-2">
Compares one part to the total. This is equivalent to a
fraction or percentage.
</p>
<div className="font-mono text-center bg-white py-1 rounded text-amber-700 text-sm">
Boys : Total = 3 : 5
</div>
<p className="text-xs text-slate-500 mt-2">
Means: boys make up 3 out of 5 = 60% of the total group.
</p>
</div>
</div>
<div className="mt-4 bg-slate-100 p-4 rounded-xl border-l-4 border-amber-500 text-sm">
<p className="font-bold text-amber-900 mb-1">
Cross-Multiplication for Proportions
</p>
<p className="text-slate-700 mb-2">
When two ratios are equal (a proportion), cross-multiply to
solve for an unknown:
</p>
<div className="font-mono text-center bg-white py-2 rounded text-slate-700 font-bold mb-1">
<Frac n="a" d="b" /> = <Frac n="c" d="d" /> a × d = b × c
</div>
<p className="text-xs text-slate-500">
Example: <Frac n="3" d="4" /> = <Frac n="x" d="20" /> 3 × 20 =
4 × x 60 = 4x x = <strong>15</strong>
</p>
</div>
<div className="mt-4 bg-amber-50 border border-amber-200 rounded-xl p-4 text-sm">
<p className="font-bold text-amber-900 mb-1">
The k-Multiplier Method
</p>
<p className="text-slate-700">
If a ratio is a : b, the actual amounts are <strong>ak</strong>{" "}
and <strong>bk</strong> for some positive number k. Use this
when you know the ratio and the total.
</p>
<div className="font-mono text-center bg-white py-1 rounded text-amber-700 text-sm mt-2">
Ratio 3:2, Total = 25 3k + 2k = 25 k = 5 Parts: 15 and 10
</div>
</div>
<p className="text-base text-slate-600 mt-4">
Use the tool below to see how scaling a ratio (multiplying by{" "}
<em>k</em>) keeps the proportions the same.
</p>
</div>
<RatioVisualizerWidget />
<button
onClick={() => scrollToSection(1)}
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
>
Next: Unit Conversions{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 2: Unit Conversions */}
<section
ref={(el) => {
sectionsRef.current[1] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Dimensional Analysis
</h2>
<div className="prose prose-slate text-lg text-slate-600 mb-8">
<p>
To convert units, multiply by conversion fractions equal to 1
(like <strong>5280 ft per mile</strong>). Arrange the fractions so
the units you <em>don't</em> want cancel out (top and bottom).
</p>
<div className="mt-4 bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">
Step-by-Step Chain Multiplication
</p>
<ol className="list-decimal list-inside space-y-1 text-sm text-slate-700">
<li>
Write the starting value as a fraction (e.g., 60 miles / 1
hour).
</li>
<li>
Multiply by a conversion fraction that cancels the unit you
want to remove.
</li>
<li>Repeat until only the desired units remain.</li>
<li>
Multiply all numerators, multiply all denominators, then
divide.
</li>
</ol>
</div>
<div className="mt-4 bg-slate-100 p-4 rounded-xl border-l-4 border-amber-500 text-sm">
<p className="font-bold text-amber-900 mb-2">
Worked Example: Convert 60 miles/hour to feet/second
</p>
<div className="font-mono text-slate-700 space-y-2 text-xs">
<div className="bg-white rounded p-2">
<span className="text-amber-700 font-bold">Step 1:</span>{" "}
Start with 60 miles/hr
</div>
<div className="bg-white rounded p-2">
<span className="text-amber-700 font-bold">Step 2:</span> ×
(5280 ft / 1 mile) cancels "miles"
</div>
<div className="bg-white rounded p-2">
<span className="text-amber-700 font-bold">Step 3:</span> × (1
hr / 3600 sec) cancels "hours"
</div>
<div className="bg-white rounded p-3 border-2 border-amber-300">
<span className="text-amber-700 font-bold">Result:</span>{" "}
<Frac n="60 × 5280" d="1 × 3600" /> ={" "}
<Frac n="316,800" d="3600" /> ={" "}
<strong className="text-amber-800">88 feet per second</strong>
</div>
</div>
<p className="text-xs text-slate-500 mt-2">
Notice how miles and hours both cancel, leaving only feet/second
exactly what was asked for.
</p>
</div>
</div>
<UnitConversionWidget />
<button
onClick={() => scrollToSection(2)}
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
>
Next: Percent Changes{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 3: Percent Increases & Decreases */}
<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">
Percent Increases & Decreases
</h2>
<div className="prose prose-slate text-lg text-slate-600 mb-8">
<p>
The most important SAT skill for percentages is using{" "}
<strong>multipliers</strong> a single number that captures both
the original value and the change.
</p>
<div className="grid md:grid-cols-2 gap-4 mt-4">
<div className="bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">
Percent Increase
</p>
<div className="font-mono text-center bg-white py-2 rounded text-amber-700 font-bold mb-2">
New = Original × (1 + r)
</div>
<p className="text-xs text-slate-600">
Where r is the rate as a decimal (e.g., 30% r = 0.30)
</p>
<p className="text-xs text-slate-500 mt-1">
Example: $200 increased by 15% 200 × 1.15 ={" "}
<strong>$230</strong>
</p>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">
Percent Decrease
</p>
<div className="font-mono text-center bg-white py-2 rounded text-amber-700 font-bold mb-2">
New = Original × (1 r)
</div>
<p className="text-xs text-slate-600">
Where r is the rate as a decimal (e.g., 25% r = 0.25)
</p>
<p className="text-xs text-slate-500 mt-1">
Example: $200 decreased by 25% 200 × 0.75 ={" "}
<strong>$150</strong>
</p>
</div>
</div>
<div className="mt-4 bg-slate-100 p-4 rounded-xl border-l-4 border-amber-500 text-sm">
<p className="font-bold text-amber-900 mb-1">
Finding the Percent Change
</p>
<div className="font-mono text-center bg-white py-2 rounded text-slate-700 font-bold mb-1">
% Change = <Frac n="New Old" d="Old" /> × 100
</div>
<p className="text-xs text-slate-600">
Positive result = increase. Negative result = decrease.
</p>
<p className="text-xs text-slate-500 mt-1">
Example: Old = 80, New = 100 <Frac n="100 80" d="80" /> ×
100 = <strong>25% increase</strong>
</p>
</div>
</div>
<PercentChangeWidget />
<button
onClick={() => scrollToSection(3)}
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
>
Next: Multi-Step Changes{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 4: Multi-Step Changes */}
<section
ref={(el) => {
sectionsRef.current[3] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Multi-Step Percent Changes
</h2>
<div className="prose prose-slate text-lg text-slate-600 mb-8">
<p>
<strong>Trap Alert:</strong> Percentages do not simply add up. An
increase of 20% followed by a decrease of 20% does{" "}
<strong>not</strong> bring you back to the start!
</p>
<div className="mt-4 bg-rose-50 border border-rose-200 rounded-xl p-5">
<p className="font-bold text-rose-900 mb-2">
Why 20% Up then 20% Down 0%
</p>
<div className="font-mono text-sm text-slate-700 space-y-1">
<div>
Start: <strong>$100</strong>
</div>
<div>
+20%: 100 × 1.20 = <strong>$120</strong>
</div>
<div>
20%: 120 × 0.80 = <strong>$96</strong> not $100!
</div>
</div>
<p className="text-xs text-slate-600 mt-2">
The decrease applies to the <em>new</em> (larger) amount, not
the original.
</p>
</div>
<div className="mt-4 bg-amber-50 border border-amber-200 rounded-xl p-5">
<p className="font-bold text-amber-900 mb-2">
The Multiplier Chain Method
</p>
<p className="text-sm text-slate-700 mb-2">
For any sequence of percent changes, multiply all the
multipliers together:
</p>
<div className="font-mono text-center bg-white py-2 rounded text-amber-700 font-bold mb-2">
Final = Original × m × m × m × ...
</div>
<div className="font-mono text-sm text-slate-700 space-y-1">
<div>
+20% then 20%: 1.20 × 0.80 = <strong>0.96</strong> {" "}
<strong>4% net decrease</strong>
</div>
<div>
+50% then 50%: 1.50 × 0.50 = <strong>0.75</strong> {" "}
<strong>25% net decrease</strong>
</div>
</div>
</div>
<div className="mt-4 bg-slate-100 p-4 rounded-xl border-l-4 border-amber-500 text-sm">
<p className="font-bold text-amber-900 mb-1">
Compound Interest (Same Idea!)
</p>
<div className="font-mono text-center bg-white py-2 rounded text-slate-700 font-bold mb-1">
A = P × (1 + r)
</div>
<p className="text-xs text-slate-600">
P = principal, r = annual rate (decimal), n = number of years.
This is just the multiplier chain with the same multiplier
repeated n times.
</p>
<p className="text-xs text-slate-500 mt-1">
Example: $1,000 at 5% for 3 years 1000 × (1.05)³ ={" "}
<strong>$1,157.63</strong>
</p>
</div>
</div>
<MultiStepPercentWidget />
<button
onClick={() => scrollToSection(4)}
className="mt-12 group flex items-center text-amber-600 font-bold hover:text-amber-800 transition-colors"
>
Next: Practice Quiz{" "}
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" />
</button>
</section>
{/* Section 5: Quiz */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-8">
Practice Time
</h2>
{allQuizzes.map((quiz, idx) => (
<div key={`quiz-${idx}`} className="mb-12">
<Quiz data={quiz} />
</div>
))}
<div className="p-8 bg-amber-900 rounded-2xl text-white text-center mt-12">
<h3 className="text-2xl font-bold mb-4">Topic Mastered!</h3>
<button
onClick={onFinish}
className="px-6 py-3 bg-white text-amber-900 font-bold rounded-full hover:bg-amber-50 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default ProportionalLesson;