feat(lessons): add new lessons for english section

This commit is contained in:
shafin-r
2026-03-09 16:41:06 +06:00
parent b5edb3554f
commit 59e601052f
38 changed files with 9086 additions and 1205 deletions

View File

@ -21,7 +21,6 @@ import { Drills } from "./pages/student/drills/page";
import { HardTestModules } from "./pages/student/hard-test-modules/page"; import { HardTestModules } from "./pages/student/hard-test-modules/page";
import { Analytics } from "./pages/student/Analytics"; import { Analytics } from "./pages/student/Analytics";
import { QuestMap } from "./pages/student/QuestMap"; import { QuestMap } from "./pages/student/QuestMap";
import ErrorPage from "./pages/ErrorPage";
import { Register } from "./pages/auth/Register"; import { Register } from "./pages/auth/Register";
function App() { function App() {
@ -40,6 +39,7 @@ function App() {
children: [ children: [
{ {
element: <StudentLayout />, element: <StudentLayout />,
children: [ children: [
{ {
path: "home", path: "home",

View File

@ -5,22 +5,16 @@ import { lazy, type ComponentType } from "react";
export type LessonId = export type LessonId =
// ---- EBRW ---- // ---- EBRW ----
| "ebrw-main-idea" | "ebrw-words-in-context"
| "ebrw-explicit-meaning" | "ebrw-text-structure-purpose"
| "ebrw-cross-text-connections"
| "ebrw-central-ideas-details"
| "ebrw-inferences" | "ebrw-inferences"
| "ebrw-graphic-displays" | "ebrw-command-of-evidence"
| "ebrw-craft-structure" | "ebrw-boundaries"
| "ebrw-vocab-precise" | "ebrw-form-structure-sense"
| "ebrw-vocab-meaning"
| "ebrw-expression-ideas"
| "ebrw-transitions" | "ebrw-transitions"
| "ebrw-commas" | "ebrw-rhetorical-synthesis"
| "ebrw-semicolons-colons"
| "ebrw-dashes-apostrophes"
| "ebrw-subject-verb"
| "ebrw-pronouns"
| "ebrw-verbs"
| "ebrw-sentence-structure"
// ---- MATH ---- // ---- MATH ----
| "alg-linear-eq-1var" | "alg-linear-eq-1var"
@ -45,55 +39,45 @@ export type LessonId =
| "geom-circles"; | "geom-circles";
// ---- EBRW ---- // ---- EBRW ----
const EBRWMainIdea = lazy( const EBRWWordsInContext = lazy(
() => import("../pages/student/lessons/EBRWMainIdeaLesson"), () => import("../pages/student/lessons/EBRWWordsInContextLesson"),
); );
const EBRWExplicitMeaning = lazy(
() => import("../pages/student/lessons/EBRWExplicitMeaningLesson"), const EBRWTextStructurePurpose = lazy(
() => import("../pages/student/lessons/EBRWTextStructurePurposeLesson"),
); );
const EBRWCrossText = lazy(
() => import("../pages/student/lessons/EBRWCrossTextLesson"),
);
const EBRWCentralIdeas = lazy(
() => import("../pages/student/lessons/EBRWCentralIdeasLesson"),
);
const EBRWInferences = lazy( const EBRWInferences = lazy(
() => import("../pages/student/lessons/EBRWInferencesLesson"), () => import("../pages/student/lessons/EBRWInferencesLesson"),
); );
const EBRWGraphicDisplays = lazy(
() => import("../pages/student/lessons/EBRWGraphicDisplaysLesson"), const EBRWCommandEvidence = lazy(
() => import("../pages/student/lessons/EBRWCommandEvidenceLesson"),
); );
const EBRWCraftStructure = lazy(
() => import("../pages/student/lessons/EBRWCraftStructureLesson"), const EBRWBoundaries = lazy(
() => import("../pages/student/lessons/EBRWBoundariesLesson"),
); );
const EBRWVocabPrecise = lazy(
() => import("../pages/student/lessons/EBRWVocabPreciseLesson"), const EBRWFormStructureSense = lazy(
); () => import("../pages/student/lessons/EBRWFormStructureSenseLesson"),
const EBRWVocabMeaning = lazy(
() => import("../pages/student/lessons/EBRWVocabMeaningLesson"),
);
const EBRWExpressionIdeas = lazy(
() => import("../pages/student/lessons/EBRWExpressionIdeasLesson"),
); );
const EBRWTransitions = lazy( const EBRWTransitions = lazy(
() => import("../pages/student/lessons/EBRWTransitionsLesson"), () => import("../pages/student/lessons/EBRWTransitionsLesson"),
); );
const EBRWCommas = lazy(
() => import("../pages/student/lessons/EBRWCommasLesson"),
);
const EBRWSemicolonsColons = lazy(
() => import("../pages/student/lessons/EBRWSemicolonsColonsLesson"),
);
const EBRWDashesApostrophes = lazy(
() => import("../pages/student/lessons/EBRWDashesApostrophesLesson"),
);
const EBRWSubjectVerb = lazy(
() => import("../pages/student/lessons/EBRWSubjectVerbLesson"),
);
const EBRWPronouns = lazy(
() => import("../pages/student/lessons/EBRWPronounsLesson"),
);
const EBRWVerbs = lazy(
() => import("../pages/student/lessons/EBRWVerbsLesson"),
);
const EBRWSentenceStructure = lazy(
() => import("../pages/student/lessons/EBRWSentenceStructureLesson"),
);
const EBRWRhetoricalSynthesis = lazy(
() => import("../pages/student/lessons/EBRWRhetoricalSynthesisLesson"),
);
// ---- MATH ---- // ---- MATH ----
const AlgLinearEq1Var = lazy( const AlgLinearEq1Var = lazy(
() => import("../pages/student/lessons/LinearEq1VarLesson"), () => import("../pages/student/lessons/LinearEq1VarLesson"),
@ -158,24 +142,19 @@ const GeomCircles = lazy(
// ---- Registry Map ---- // ---- Registry Map ----
export const LESSON_COMPONENT_MAP: Record<LessonId, ComponentType> = { export const LESSON_COMPONENT_MAP: Record<LessonId, ComponentType> = {
// EBRW // ---- EBRW ----
"ebrw-main-idea": EBRWMainIdea, "ebrw-words-in-context": EBRWWordsInContext,
"ebrw-explicit-meaning": EBRWExplicitMeaning, "ebrw-text-structure-purpose": EBRWTextStructurePurpose,
"ebrw-cross-text-connections": EBRWCrossText,
"ebrw-central-ideas-details": EBRWCentralIdeas,
"ebrw-inferences": EBRWInferences, "ebrw-inferences": EBRWInferences,
"ebrw-graphic-displays": EBRWGraphicDisplays, "ebrw-command-of-evidence": EBRWCommandEvidence,
"ebrw-craft-structure": EBRWCraftStructure, "ebrw-boundaries": EBRWBoundaries,
"ebrw-vocab-precise": EBRWVocabPrecise, "ebrw-form-structure-sense": EBRWFormStructureSense,
"ebrw-vocab-meaning": EBRWVocabMeaning,
"ebrw-expression-ideas": EBRWExpressionIdeas,
"ebrw-transitions": EBRWTransitions, "ebrw-transitions": EBRWTransitions,
"ebrw-commas": EBRWCommas, "ebrw-rhetorical-synthesis": EBRWRhetoricalSynthesis,
"ebrw-semicolons-colons": EBRWSemicolonsColons,
"ebrw-dashes-apostrophes": EBRWDashesApostrophes, // ---- MATH ----
"ebrw-subject-verb": EBRWSubjectVerb,
"ebrw-pronouns": EBRWPronouns,
"ebrw-verbs": EBRWVerbs,
"ebrw-sentence-structure": EBRWSentenceStructure,
// MATH
"alg-linear-eq-1var": AlgLinearEq1Var, "alg-linear-eq-1var": AlgLinearEq1Var,
"alg-linear-eq-2var": AlgLinearEq2Var, "alg-linear-eq-2var": AlgLinearEq2Var,
"alg-linear-functions": AlgLinearFunctions, "alg-linear-functions": AlgLinearFunctions,

View File

@ -109,7 +109,7 @@ export default function LessonShell({
const childArray = React.Children.toArray(children); const childArray = React.Children.toArray(children);
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen lesson-bg"> <div className="flex flex-col lg:flex-row min-h-screen">
{/* ── Mobile toggle ── */} {/* ── Mobile toggle ── */}
<button <button
onClick={() => setSidebarOpen(!sidebarOpen)} onClick={() => setSidebarOpen(!sidebarOpen)}
@ -180,7 +180,7 @@ export default function LessonShell({
</aside> </aside>
{/* ── Main content ── */} {/* ── Main content ── */}
<div className="flex-1 lg:ml-64 max-w-4xl mx-auto w-full"> <div className="flex-1 lg:ml-64 mx-auto md:p-12 max-w-full">
{childArray.map((child, i) => ( {childArray.map((child, i) => (
<section <section
key={i} key={i}

View File

@ -0,0 +1,114 @@
import React, { useState, useEffect, useRef } from 'react';
export interface RevealCard {
label: string;
sublabel?: string;
content: string;
}
const COLORS: Record<string, {
text: string; activeBg: string; activeBorder: string;
hoverBorder: string; btn: string;
}> = {
fuchsia: { text: 'text-fuchsia-700', activeBg: 'bg-fuchsia-50', activeBorder: 'border-fuchsia-300', hoverBorder: 'hover:border-fuchsia-300', btn: 'text-fuchsia-600' },
teal: { text: 'text-teal-700', activeBg: 'bg-teal-50', activeBorder: 'border-teal-300', hoverBorder: 'hover:border-teal-300', btn: 'text-teal-600' },
purple: { text: 'text-purple-700', activeBg: 'bg-purple-50', activeBorder: 'border-purple-300', hoverBorder: 'hover:border-purple-300', btn: 'text-purple-600' },
rose: { text: 'text-rose-700', activeBg: 'bg-rose-50', activeBorder: 'border-rose-300', hoverBorder: 'hover:border-rose-300', btn: 'text-rose-600' },
indigo: { text: 'text-indigo-700', activeBg: 'bg-indigo-50', activeBorder: 'border-indigo-300', hoverBorder: 'hover:border-indigo-300', btn: 'text-indigo-600' },
amber: { text: 'text-amber-700', activeBg: 'bg-amber-50', activeBorder: 'border-amber-300', hoverBorder: 'hover:border-amber-300', btn: 'text-amber-600' },
};
interface Props {
cards: RevealCard[];
columns?: 2 | 3 | 4 | 5;
accentColor?: string;
}
const RevealCardGrid: React.FC<Props> = ({ cards, columns = 3, accentColor = 'fuchsia' }) => {
const [revealed, setRevealed] = useState<Set<number>>(new Set());
const [visible, setVisible] = useState(false);
const [entered, setEntered] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const c = COLORS[accentColor] || COLORS.fuchsia;
/* Entrance trigger — animate cards when grid scrolls into view */
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver(
entries => { if (entries[0]?.isIntersecting) { setVisible(true); obs.disconnect(); } },
{ threshold: 0.1 },
);
obs.observe(el);
return () => obs.disconnect();
}, []);
/* Clear stagger delays after entrance finishes so tap interactions are instant */
useEffect(() => {
if (!visible) return;
const t = setTimeout(() => setEntered(true), Math.min(cards.length * 50, 600) + 400);
return () => clearTimeout(t);
}, [visible, cards.length]);
const toggle = (i: number) =>
setRevealed(prev => { const s = new Set(prev); s.has(i) ? s.delete(i) : s.add(i); return s; });
const allDone = revealed.size === cards.length;
const colCls =
columns === 2 ? 'grid-cols-1 sm:grid-cols-2'
: columns === 4 ? 'grid-cols-2 sm:grid-cols-4'
: columns === 5 ? 'grid-cols-2 sm:grid-cols-5'
: 'grid-cols-2 sm:grid-cols-3';
return (
<div ref={ref} className="space-y-3">
<div className="flex items-center justify-between">
<p className="text-xs text-slate-400 font-medium">
{revealed.size}/{cards.length} revealed
</p>
<button
onClick={() => setRevealed(allDone ? new Set() : new Set(cards.map((_, i) => i)))}
className={`text-xs font-bold ${c.btn} hover:underline transition-colors`}
>
{allDone ? 'Hide All' : 'Reveal All'}
</button>
</div>
<div className={`grid ${colCls} gap-2`}>
{cards.map((card, i) => {
const open = revealed.has(i);
return (
<button
key={i}
onClick={() => toggle(i)}
aria-expanded={open}
className={[
'text-left rounded-xl p-3 border transition-all duration-300 ease-out',
visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-3',
open
? `${c.activeBg} ${c.activeBorder} shadow-sm`
: `bg-slate-50 border-slate-200 ${c.hoverBorder} hover:shadow-sm`,
].join(' ')}
style={{ transitionDelay: !entered ? `${Math.min(i * 50, 600)}ms` : '0ms' }}
>
<p className={`font-bold ${c.text} text-xs mb-0.5`}>{card.label}</p>
{card.sublabel && (
<p className="text-[10px] text-slate-500 font-medium mb-1">{card.sublabel}</p>
)}
<div className={`transition-opacity duration-200 ${open ? 'opacity-100' : 'opacity-50'}`}>
{open ? (
<p className="text-xs text-slate-600 leading-relaxed">{card.content}</p>
) : (
<p className="text-xs text-slate-400 italic">tap to reveal</p>
)}
</div>
</button>
);
})}
</div>
</div>
);
};
export default RevealCardGrid;

View File

@ -0,0 +1,36 @@
import { useEffect } from 'react';
/**
* Observes all `.scroll-reveal` elements in the DOM and adds the `revealed`
* class when they scroll into view. Works with the stagger-1 … stagger-10
* utility classes defined in index.css for sequenced entrance animations.
*
* Call once at the top of a lesson component:
* useScrollReveal();
*/
export default function useScrollReveal() {
useEffect(() => {
const sel = [
'.scroll-reveal:not(.revealed)',
'.scroll-reveal-left:not(.revealed)',
'.scroll-reveal-right:not(.revealed)',
'.scroll-reveal-scale:not(.revealed)',
].join(',');
const els = document.querySelectorAll(sel);
if (!els.length) return;
const obs = new IntersectionObserver(
entries =>
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('revealed');
obs.unobserve(e.target);
}
}),
{ threshold: 0.12, rootMargin: '0px 0px -60px 0px' },
);
els.forEach(el => obs.observe(el));
return () => obs.disconnect();
}, []);
}

View File

@ -1,4 +1,4 @@
import type { PracticeQuestion } from "../../types/lesson"; import { type PracticeQuestion } from "../../types/lesson";
export const CENTRAL_IDEAS_EASY: PracticeQuestion[] = [ export const CENTRAL_IDEAS_EASY: PracticeQuestion[] = [
{ {

View File

@ -151,8 +151,8 @@ export const RW_TOPICS: TopicRegistry = {
id: "transitions", id: "transitions",
name: "Transitions", name: "Transitions",
section: "rw", section: "rw",
category: "Standard English", category: "Expression of Ideas",
color: "purple", color: "rose",
questions: { questions: {
easy: TRANSITIONS_EASY, easy: TRANSITIONS_EASY,
medium: TRANSITIONS_MEDIUM, medium: TRANSITIONS_MEDIUM,

View File

@ -2,191 +2,363 @@ import { useState, useEffect } from "react";
import type { FormEvent } from "react"; import type { FormEvent } from "react";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "../../stores/authStore"; import { useAuthStore } from "../../stores/authStore";
import { Loader2, Mail, Lock } from "lucide-react"; import { Loader2, Mail, Lock, Target, Clock, BarChart2 } from "lucide-react";
interface LocationState { interface LocationState {
from?: { pathname: string }; from?: { pathname: string };
} }
const DOTS = [
{ size: 12, color: "#f97316", top: "8%", left: "6%", delay: "0s" },
{ size: 7, color: "#a855f7", top: "22%", left: "3%", delay: "1.2s" },
{ size: 9, color: "#22c55e", top: "65%", left: "5%", delay: "0.6s" },
{ size: 8, color: "#f43f5e", top: "80%", left: "8%", delay: "2.1s" },
{ size: 12, color: "#3b82f6", top: "10%", right: "6%", delay: "1.8s" },
{ size: 7, color: "#eab308", top: "40%", right: "3%", delay: "0.9s" },
{ size: 10, color: "#a855f7", top: "72%", right: "5%", delay: "0.4s" },
{ size: 8, color: "#f97316", top: "55%", right: "8%", delay: "1.5s" },
];
const STYLES = ` const STYLES = `
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
.lg-screen { *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.lg-root {
min-height: 100vh; min-height: 100vh;
background: #fffbf4; display: flex;
font-family: 'Nunito', sans-serif; font-family: 'Nunito', sans-serif;
background: #fffbf4;
}
/* ─── LEFT PANEL ─── */
.lg-left {
position: relative; position: relative;
width: 50%;
min-height: 100vh;
background: linear-gradient(160deg, #060d1f 0%, #0f2044 40%, #0e3476 75%, #1a56c4 100%);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 3rem 3.5rem;
overflow: hidden; overflow: hidden;
flex-shrink: 0;
}
/* Animated grid */
.lg-grid {
position: absolute; inset: 0; pointer-events: none;
background-image:
linear-gradient(rgba(99,179,255,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(99,179,255,0.06) 1px, transparent 1px);
background-size: 52px 52px;
animation: gridScroll 25s linear infinite;
}
@keyframes gridScroll {
from { background-position: 0 0; }
to { background-position: 52px 52px; }
}
/* Radial glow spots */
.lg-glow {
position: absolute; pointer-events: none; border-radius: 50%;
filter: blur(60px);
}
.lg-glow-1 { width: 380px; height: 380px; background: #1d4ed8; opacity: 0.35; top: -120px; right: -80px; animation: glowPulse 8s ease-in-out infinite; }
.lg-glow-2 { width: 280px; height: 280px; background: #0ea5e9; opacity: 0.2; bottom: -60px; left: -60px; animation: glowPulse 10s ease-in-out infinite 2s; }
.lg-glow-3 { width: 200px; height: 200px; background: #f97316; opacity: 0.12; top: 55%; left: 55%; animation: glowPulse 12s ease-in-out infinite 1s; }
@keyframes glowPulse {
0%,100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.2); opacity: 0.35; }
}
/* ── Floating score card ── */
.lg-score-card {
position: absolute;
top: 9%; right: 6%;
width: 162px;
background: linear-gradient(135deg, rgba(255,255,255,0.11), rgba(255,255,255,0.05));
border: 1px solid rgba(255,255,255,0.18);
border-radius: 20px;
padding: 1.1rem 1.2rem 1rem;
backdrop-filter: blur(16px);
box-shadow: 0 8px 32px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.15);
animation: floatA 6s ease-in-out infinite;
z-index: 2;
}
@keyframes floatA {
0%,100% { transform: translateY(0) rotate(-1.5deg); }
50% { transform: translateY(-14px) rotate(0.5deg); }
}
.lg-score-tag {
font-size: 0.58rem; font-weight: 800; letter-spacing: 0.14em;
text-transform: uppercase; color: #7dd3fc; margin-bottom: 0.5rem;
display: flex; align-items: center; gap: 0.3rem;
}
.lg-score-tag::before { content:''; width:6px;height:6px;border-radius:50%;background:#34d399;display:inline-block;box-shadow:0 0 6px #34d399; }
.lg-score-num {
font-size: 2.2rem; font-weight: 900; color: white; line-height: 1; margin-bottom: 0.2rem;
text-shadow: 0 2px 12px rgba(99,179,255,0.4);
}
.lg-score-delta {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.7rem; font-weight: 700; color: #4ade80;
display: flex; align-items: center; gap: 0.2rem; margin-bottom: 0.75rem;
}
.lg-bars {
display: flex; align-items: flex-end; gap: 3px; height: 40px;
}
.lg-bar {
flex: 1; border-radius: 3px 3px 0 0;
animation: barGrow 1s cubic-bezier(0.34,1.56,0.64,1) both;
transform-origin: bottom;
}
@keyframes barGrow {
from { transform: scaleY(0); }
to { transform: scaleY(1); }
}
/* ── Streak pill ── */
.lg-streak {
position: absolute;
bottom: 12%; left: 5%;
background: linear-gradient(135deg, rgba(249,115,22,0.18), rgba(239,68,68,0.1));
border: 1px solid rgba(249,115,22,0.4);
border-radius: 100px;
padding: 0.65rem 1.2rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(249,115,22,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
display: flex; align-items: center; gap: 0.65rem;
animation: floatB 7s ease-in-out infinite 1s;
z-index: 2;
}
@keyframes floatB {
0%,100% { transform: translateY(0) rotate(1deg); }
50% { transform: translateY(-10px) rotate(-0.5deg); }
}
.lg-streak-fire { font-size: 1.5rem; filter: drop-shadow(0 0 8px #f97316); }
.lg-streak-text strong { display:block; font-size:0.85rem; font-weight:900; color:white; }
.lg-streak-text span { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:600; color:#fed7aa; }
/* ── Questions badge ── */
.lg-q-badge {
position: absolute;
bottom: 28%; right: 5%;
background: linear-gradient(135deg, rgba(139,92,246,0.18), rgba(99,102,241,0.1));
border: 1px solid rgba(139,92,246,0.4);
border-radius: 16px;
padding: 0.7rem 1.05rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(139,92,246,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
animation: floatA 9s ease-in-out infinite 0.5s;
z-index: 2;
}
.lg-q-badge p { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:700; color:#c4b5fd; margin-bottom:0.15rem; }
.lg-q-badge strong { font-size:1.35rem; font-weight:900; color:white; display:block; text-shadow:0 0 20px rgba(167,139,250,0.5); }
/* ── Accuracy ring ── */
.lg-ring-wrap {
position: absolute;
top: 52%; left: 6%;
width: 80px; height: 80px;
animation: floatB 10s ease-in-out infinite 0.8s;
z-index: 2;
}
.lg-ring-svg { width: 80px; height: 80px; transform: rotate(-90deg); }
.lg-ring-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 5; }
.lg-ring-fill { fill: none; stroke: #34d399; stroke-width: 5; stroke-linecap: round;
stroke-dasharray: 188; stroke-dashoffset: 18;
animation: ringFill 1.8s ease both 0.3s; }
@keyframes ringFill {
from { stroke-dashoffset: 188; }
to { stroke-dashoffset: 18; }
}
.lg-ring-label {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.lg-ring-label strong { font-size: 0.95rem; font-weight: 900; color: white; line-height: 1; }
.lg-ring-label span { font-family:'Nunito Sans',sans-serif; font-size: 0.52rem; font-weight: 700; color: #7dd3fc; text-transform: uppercase; letter-spacing: 0.05em; }
/* Scattered glitter dots */
.lg-glitter { position: absolute; border-radius: 50%; pointer-events: none; animation: glitterFloat 8s ease-in-out infinite; }
@keyframes glitterFloat {
0%,100% { transform: translateY(0) scale(1); opacity: 0.5; }
50% { transform: translateY(-16px) scale(1.3); opacity: 0.9; }
}
/* Stars */
.lg-star { position: absolute; pointer-events: none; animation: starTwinkle 2.5s ease-in-out infinite; color: #fde68a; }
@keyframes starTwinkle {
0%,100% { opacity: 0.4; transform: scale(0.9) rotate(0deg); }
50% { opacity: 1; transform: scale(1.4) rotate(20deg); }
}
/* Thin decorative rings */
.lg-deco-ring {
position: absolute; border-radius: 50%; pointer-events: none;
border: 1.5px solid rgba(255,255,255,0.07);
animation: decoSpin 40s linear infinite;
}
@keyframes decoSpin { to { transform: rotate(360deg); } }
/* Panel content */
.lg-panel-content {
position: relative; z-index: 3;
display: flex; flex-direction: column;
align-items: flex-start; gap: 2rem;
width: 100%;
}
.lg-panel-logo { display: flex; align-items: center; gap: 0.75rem; }
.lg-panel-logo-badge {
width: 46px; height: 46px; border-radius: 13px;
background: linear-gradient(135deg, #f97316, #ef4444);
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
padding: 2rem 1.25rem; box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 8px 20px rgba(249,115,22,0.45);
font-size: 1.3rem;
}
.lg-panel-logo-text { font-size: 1.35rem; font-weight: 900; color: white; letter-spacing: -0.02em; }
.lg-panel-headline { display: flex; flex-direction: column; gap: 0.6rem; }
.lg-panel-headline h2 {
font-size: 2.6rem; font-weight: 900; line-height: 1.1;
color: white; letter-spacing: -0.035em;
text-shadow: 0 4px 24px rgba(0,0,0,0.3);
}
.lg-panel-headline h2 span {
background: linear-gradient(90deg, #fbbf24 0%, #f97316 60%, #ef4444 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 16px rgba(249,115,22,0.4));
}
.lg-panel-headline p {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.92rem; font-weight: 600; color: #93c5fd; line-height: 1.65;
} }
/* Blobs */ /* Stats row */
.lg-blob { position:fixed;pointer-events:none;z-index:0; } .lg-stats { display: flex; gap: 0.75rem; width: 100%; }
.lg-blob-1 { width:280px;height:280px;background:#fde68a;top:-100px;left:-100px;border-radius:60% 40% 70% 30%/50% 60% 40% 50%;animation:lgWobble1 14s ease-in-out infinite; } .lg-stat {
.lg-blob-2 { width:220px;height:220px;background:#a5f3c0;bottom:-60px;left:4%;border-radius:40% 60% 30% 70%/60% 40% 60% 40%;animation:lgWobble2 16s ease-in-out infinite; } flex: 1;
.lg-blob-3 { width:250px;height:250px;background:#fbcfe8;top:10%;right:-70px;border-radius:70% 30% 50% 50%/40% 60% 40% 60%;animation:lgWobble1 18s ease-in-out infinite reverse; } background: linear-gradient(135deg, rgba(255,255,255,0.09), rgba(255,255,255,0.04));
.lg-blob-4 { width:180px;height:180px;background:#bfdbfe;bottom:8%;right:0;border-radius:50% 50% 30% 70%/60% 40% 60% 40%;animation:lgWobble2 12s ease-in-out infinite; } border: 1px solid rgba(255,255,255,0.13);
border-radius: 18px; padding: 0.9rem 0.85rem;
@keyframes lgWobble1 { backdrop-filter: blur(12px);
0%,100%{border-radius:60% 40% 70% 30%/50% 60% 40% 50%;transform:translate(0,0) rotate(0deg);} box-shadow: 0 4px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.12);
50%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(14px,18px) rotate(8deg);} display: flex; flex-direction: column; gap: 0.2rem;
animation: statSlide 0.5s ease both;
} }
@keyframes lgWobble2 { .lg-stat:nth-child(1) { animation-delay: 0.1s; }
0%,100%{border-radius:40% 60% 30% 70%/60% 40% 60% 40%;transform:translate(0,0) rotate(0deg);} .lg-stat:nth-child(2) { animation-delay: 0.2s; }
50%{border-radius:60% 40% 70% 30%/40% 60% 40% 60%;transform:translate(-12px,14px) rotate(-6deg);} .lg-stat:nth-child(3) { animation-delay: 0.3s; }
@keyframes statSlide {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
} }
.lg-stat-icon { width: 28px; height: 28px; border-radius: 8px; display:flex;align-items:center;justify-content:center; margin-bottom:0.3rem; }
.lg-stat strong { font-size: 1.3rem; font-weight: 900; color: white; line-height: 1; }
.lg-stat span { font-family:'Nunito Sans',sans-serif; font-size:0.64rem; font-weight:600; color:#93c5fd; }
.lg-dot { position:fixed;border-radius:50%;pointer-events:none;z-index:0;opacity:0.28;animation:lgFloat 7s ease-in-out infinite; } /* Social proof */
@keyframes lgFloat { .lg-social { display: flex; align-items: center; gap: 0.75rem; }
0%,100%{transform:translateY(0) rotate(0deg);} .lg-avs { display: flex; }
50%{transform:translateY(-14px) rotate(180deg);} .lg-av {
width: 30px; height: 30px; border-radius: 50%;
border: 2px solid #0f2044; margin-left: -8px;
display: flex; align-items: center; justify-content: center;
font-size: 0.6rem; font-weight: 800; color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
} }
.lg-av:first-child { margin-left: 0; }
.lg-social p { font-family:'Nunito Sans',sans-serif; font-size:0.75rem; font-weight:700; color:#93c5fd; }
.lg-social p strong { color:#fbbf24; }
/* Card */ /* ─── RIGHT PANEL ─── */
.lg-card { .lg-right {
flex: 1;
display: flex; align-items: center; justify-content: center;
padding: 3rem 4rem;
position: relative; overflow: hidden;
}
.lg-bg-dot { position:absolute;border-radius:50%;pointer-events:none;opacity:0.09;animation:bgFloat 10s ease-in-out infinite; }
@keyframes bgFloat { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-14px);} }
.lg-form-wrap {
position: relative; z-index: 1; position: relative; z-index: 1;
width: 100%; max-width: 400px; width: 100%; max-width: 400px;
background: white; border: 2.5px solid #f3f4f6; display: flex; flex-direction: column; gap: 2rem;
border-radius: 28px; animation: formPop 0.55s cubic-bezier(0.34,1.56,0.64,1) both;
box-shadow: 0 12px 40px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.04);
padding: 2.25rem 2rem 2rem;
display: flex; flex-direction: column; gap: 1.75rem;
animation: lgPopIn 0.5s cubic-bezier(0.34,1.56,0.64,1) both;
} }
@keyframes lgPopIn { @keyframes formPop {
from { opacity:0; transform:scale(0.9) translateY(20px); } from { opacity:0; transform:translateY(22px) scale(0.97); }
to { opacity:1; transform:scale(1) translateY(0); } to { opacity:1; transform:translateY(0) scale(1); }
} }
/* Logo area */ .lg-form-header { display:flex;flex-direction:column;gap:0.4rem; }
.lg-logo-wrap { .lg-form-header h1 { font-size:2rem;font-weight:900;color:#1e1b4b;letter-spacing:-0.03em;line-height:1.2; }
display: flex; flex-direction: column; align-items: center; gap: 0.85rem; .lg-form-header p { font-family:'Nunito Sans',sans-serif;font-size:0.88rem;font-weight:600;color:#9ca3af; }
}
.lg-logo-badge {
width: 64px; height: 64px; border-radius: 20px;
background: linear-gradient(135deg, #a855f7, #7c3aed);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 6px 0 #5b21b655, 0 10px 24px rgba(124,58,237,0.25);
font-size: 1.75rem;
animation: lgPopIn 0.5s cubic-bezier(0.34,1.56,0.64,1) 0.1s both;
}
.lg-title {
font-size: 1.5rem; font-weight: 900; color: #1e1b4b;
letter-spacing: -0.02em; text-align: center;
}
.lg-sub {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.82rem; font-weight: 600; color: #9ca3af;
text-align: center; margin-top: -0.25rem;
}
/* Form fields */ .lg-fields { display:flex;flex-direction:column;gap:1.1rem; }
.lg-fields { display: flex; flex-direction: column; gap: 1rem; } .lg-field { display:flex;flex-direction:column;gap:0.4rem; }
.lg-label { font-size:0.7rem;font-weight:800;letter-spacing:0.1em;text-transform:uppercase;color:#6b7280;padding-left:0.2rem; }
.lg-field { display: flex; flex-direction: column; gap: 0.4rem; } .lg-input-wrap { position:relative; }
.lg-label { .lg-input-icon { position:absolute;left:0.9rem;top:50%;transform:translateY(-50%);pointer-events:none;color:#9ca3af;transition:color 0.2s; }
font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em;
text-transform: uppercase; color: #6b7280;
padding-left: 0.25rem;
}
.lg-input-wrap { position: relative; }
.lg-input-icon {
position: absolute; left: 0.85rem; top: 50%;
transform: translateY(-50%); pointer-events: none; color: #9ca3af;
transition: color 0.2s ease;
}
.lg-input { .lg-input {
width: 100%; padding: 0.8rem 1rem 0.8rem 2.6rem; width:100%;padding:0.9rem 1rem 0.9rem 2.65rem;
background: #f9fafb; border: 2.5px solid #f3f4f6; background:#f9fafb;border:2.5px solid #f3f4f6;border-radius:14px;
border-radius: 14px; font-family:'Nunito Sans',sans-serif;font-size:0.9rem;font-weight:600;color:#1e1b4b;
font-family: 'Nunito Sans', sans-serif; outline:none;transition:all 0.2s;box-sizing:border-box;
font-size: 0.88rem; font-weight: 600; color: #1e1b4b;
outline: none; transition: all 0.2s ease;
box-sizing: border-box;
} }
.lg-input:focus { .lg-input:focus { background:white;border-color:#93c5fd;box-shadow:0 0 0 3.5px rgba(59,130,246,0.1); }
background: white; border-color: #c4b5fd; .lg-input:disabled { opacity:0.5;cursor:not-allowed; }
box-shadow: 0 0 0 3px rgba(168,85,247,0.1); .lg-input::placeholder { color:#d1d5db; }
}
.lg-input:focus ~ .lg-input-icon { color: #a855f7; }
.lg-input:disabled { opacity: 0.5; cursor: not-allowed; }
.lg-input::placeholder { color: #d1d5db; }
/* Remember me */ .lg-remember { display:flex;align-items:center;gap:0.5rem;padding:0 0.1rem; }
.lg-remember { .lg-checkbox { width:17px;height:17px;border-radius:5px;accent-color:#3b82f6;cursor:pointer;flex-shrink:0; }
display: flex; align-items: center; gap: 0.5rem; .lg-remember-label { font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#6b7280;cursor:pointer; }
padding: 0 0.1rem;
}
.lg-checkbox {
width: 18px; height: 18px; border-radius: 6px;
accent-color: #a855f7; cursor: pointer; flex-shrink: 0;
}
.lg-remember-label {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.8rem; font-weight: 600; color: #6b7280;
cursor: pointer;
}
/* Error */
.lg-error { .lg-error {
background: #fff1f2; border: 2px solid #fecdd3; background:#fff1f2;border:2px solid #fecdd3;border-radius:14px;padding:0.8rem 1rem;
border-radius: 14px; padding: 0.75rem 1rem; font-family:'Nunito Sans',sans-serif;font-size:0.82rem;font-weight:700;color:#e11d48;
font-family: 'Nunito Sans', sans-serif; display:flex;align-items:center;gap:0.5rem;
font-size: 0.82rem; font-weight: 700; color: #e11d48;
display: flex; align-items: center; gap: 0.5rem;
} }
/* Submit button */
.lg-btn { .lg-btn {
width: 100%; padding: 0.95rem; width:100%;padding:1rem;background:#f97316;color:white;border:none;border-radius:100px;cursor:pointer;
background: #f97316; color: white; border: none; font-family:'Nunito',sans-serif;font-size:1rem;font-weight:900;
border-radius: 100px; cursor: pointer; display:flex;align-items:center;justify-content:center;gap:0.5rem;
font-family: 'Nunito', sans-serif; font-size: 0.95rem; font-weight: 900; box-shadow:0 6px 0 #c2560e,0 10px 24px rgba(249,115,22,0.28);
display: flex; align-items: center; justify-content: center; gap: 0.5rem; transition:transform 0.1s,box-shadow 0.1s;
box-shadow: 0 6px 0 #c2560e, 0 8px 20px rgba(249,115,22,0.25);
transition: transform 0.1s ease, box-shadow 0.1s ease;
} }
.lg-btn:hover { transform:translateY(-2px); box-shadow:0 8px 0 #c2560e,0 12px 24px rgba(249,115,22,0.3); } .lg-btn:hover { transform:translateY(-2px);box-shadow:0 8px 0 #c2560e,0 14px 28px rgba(249,115,22,0.32); }
.lg-btn:active { transform:translateY(3px); box-shadow:0 3px 0 #c2560e; } .lg-btn:active { transform:translateY(3px);box-shadow:0 3px 0 #c2560e; }
.lg-btn:disabled { .lg-btn:disabled { background:#e5e7eb;color:#9ca3af;cursor:not-allowed;box-shadow:0 4px 0 #d1d5db; }
background: #e5e7eb; color: #9ca3af; .lg-btn:disabled:hover { transform:none;box-shadow:0 4px 0 #d1d5db; }
cursor: not-allowed; box-shadow: 0 4px 0 #d1d5db;
}
.lg-btn:disabled:hover { transform: none; box-shadow: 0 4px 0 #d1d5db; }
.lg-spinner { animation: lgSpin 0.8s linear infinite; } .lg-spinner { animation:spin 0.8s linear infinite; }
@keyframes lgSpin { to { transform: rotate(360deg); } } @keyframes spin { to { transform:rotate(360deg); } }
/* Footer hint */ .lg-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.75rem;font-weight:600;color:#9ca3af; }
.lg-footer { .lg-signup-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#9ca3af; }
text-align: center; .lg-link { color:#f97316;font-weight:800;text-decoration:none;cursor:pointer; }
font-family: 'Nunito Sans', sans-serif; .lg-link:hover { color:#ea6c00; }
font-size: 0.75rem; font-weight: 600; color: #9ca3af;
@media (max-width: 860px) {
.lg-left { display:none; }
.lg-right { padding:2rem 1.5rem; }
} }
.rg-footer {
text-align: center;
font-family: 'Nunito Sans', sans-serif;
font-size: 0.78rem; font-weight: 600; color: #9ca3af;
}
.rg-link {
color: #a855f7; font-weight: 800; text-decoration: none;
transition: color 0.2s ease;
}
.rg-link:hover { color: #7c3aed; }
`; `;
const BAR_HEIGHTS = [30, 50, 42, 75, 55, 88, 65];
const BAR_COLORS = [
"#60a5fa",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
];
const AV_COLORS = [
"linear-gradient(135deg,#3b82f6,#1d4ed8)",
"linear-gradient(135deg,#f97316,#ef4444)",
"linear-gradient(135deg,#22c55e,#15803d)",
"linear-gradient(135deg,#a855f7,#7c3aed)",
"linear-gradient(135deg,#eab308,#ca8a04)",
];
const AV_INITIALS = ["SK", "NR", "TM", "AB", "PL"];
export const Login = () => { export const Login = () => {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
@ -195,13 +367,12 @@ export const Login = () => {
const { login, isAuthenticated, isLoading, error, clearError } = const { login, isAuthenticated, isLoading, error, clearError } =
useAuthStore(); useAuthStore();
const from =
const from = (location.state as LocationState)?.from?.pathname || "/student"; (location.state as LocationState)?.from?.pathname || "/student/home";
useEffect(() => { useEffect(() => {
if (isAuthenticated) navigate("/student/home", { replace: true }); if (isAuthenticated) navigate("/student/home", { replace: true });
}, [isAuthenticated, navigate]); }, [isAuthenticated, navigate]);
useEffect(() => { useEffect(() => {
return () => clearError(); return () => clearError();
}, [clearError]); }, [clearError]);
@ -216,138 +387,323 @@ export const Login = () => {
if (isAuthenticated) return null; if (isAuthenticated) return null;
return ( return (
<div className="lg-screen"> <div className="lg-root">
<style>{STYLES}</style> <style>{STYLES}</style>
{/* Blobs */} {/* ── LEFT PANEL ── */}
<div className="lg-blob lg-blob-1" /> <div className="lg-left">
<div className="lg-blob lg-blob-2" /> {/* Background layers */}
<div className="lg-blob lg-blob-3" /> <div className="lg-grid" />
<div className="lg-blob lg-blob-4" /> <div className="lg-glow lg-glow-1" />
<div className="lg-glow lg-glow-2" />
<div className="lg-glow lg-glow-3" />
{/* Dots */} {/* Decorative spinning rings */}
{DOTS.map((d, i) => ( {[
<div { s: 260, t: "38%", l: "58%", mt: -130, ml: -130 },
key={i} {
className="lg-dot" s: 380,
style={ t: "42%",
{ l: "54%",
width: d.size, mt: -190,
height: d.size, ml: -190,
background: d.color, dir: "reverse" as const,
top: d.top, },
left: d.left, ].map((r, i) => (
right: d.right, <div
animationDelay: d.delay, key={i}
animationDuration: `${5.5 + i * 0.4}s`, className="lg-deco-ring"
} as React.CSSProperties style={
} {
/> width: r.s,
))} height: r.s,
top: r.t,
<div className="lg-card"> left: r.l,
{/* Logo + heading */} marginTop: r.mt,
<div className="lg-logo-wrap space-y-5"> marginLeft: r.ml,
<img animationDirection: r.dir ?? "normal",
src="src/assets/ed_logo.png" animationDuration: `${35 + i * 10}s`,
alt="EdBridge" } as React.CSSProperties
style={{ }
width: 600,
height: 70,
objectFit: "contain",
borderRadius: 8,
}}
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/> />
))}
<div> {/* Floating score card */}
<h1 className="lg-title">Welcome back 👋</h1> <div className="lg-score-card">
<p className="lg-sub">Sign in to continue your SAT prep</p> <p className="lg-score-tag">SAT Score</p>
<p className="lg-score-num">1480</p>
<p className="lg-score-delta"> +120 pts this month</p>
<div className="lg-bars">
{BAR_HEIGHTS.map((h, i) => (
<div
key={i}
className="lg-bar"
style={{
height: `${h}%`,
background: BAR_COLORS[i],
animationDelay: `${i * 0.08}s`,
}}
/>
))}
</div> </div>
</div> </div>
{/* Fields */} {/* Accuracy ring */}
<div className="lg-fields"> <div className="lg-ring-wrap">
{/* Email */} <svg className="lg-ring-svg" viewBox="0 0 80 80">
<div className="lg-field"> <circle className="lg-ring-bg" cx="40" cy="40" r="30" />
<label className="lg-label" htmlFor="email"> <circle className="lg-ring-fill" cx="40" cy="40" r="30" />
Email </svg>
</label> <div className="lg-ring-label">
<div className="lg-input-wrap"> <strong>94%</strong>
<Mail size={15} className="lg-input-icon" /> <span>Accuracy</span>
<input
id="email"
type="email"
className="lg-input"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
</div> </div>
</div>
{/* Password */} {/* Streak pill */}
<div className="lg-field"> <div className="lg-streak">
<label className="lg-label" htmlFor="password"> <span className="lg-streak-fire">🔥</span>
Password <div className="lg-streak-text">
</label> <strong>14-day streak!</strong>
<div className="lg-input-wrap"> <span>Keep it going</span>
<Lock size={15} className="lg-input-icon" />
<input
id="password"
type="password"
className="lg-input"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div> </div>
</div>
{/* Remember me */} {/* Questions badge */}
<div className="lg-remember"> <div className="lg-q-badge">
<input id="rememberMe" type="checkbox" className="lg-checkbox" /> <p>Questions solved</p>
<label htmlFor="rememberMe" className="lg-remember-label"> <strong>2,847</strong>
Keep me signed in </div>
</label>
</div>
{/* Error */} {/* Glitter dots */}
{error && ( {[
<div className="lg-error"> { s: 10, c: "#60a5fa", t: "13%", l: "58%", d: "0s", dur: "9s" },
<span></span> {error} { s: 7, c: "#fbbf24", t: "70%", l: "70%", d: "1s", dur: "11s" },
</div> { s: 12, c: "#34d399", t: "38%", l: "7%", d: "0.4s", dur: "8s" },
)} { s: 5, c: "#f472b6", t: "82%", l: "35%", d: "1.8s", dur: "13s" },
{ s: 8, c: "#a78bfa", t: "20%", l: "36%", d: "0.9s", dur: "10s" },
{ s: 6, c: "#fb923c", t: "62%", l: "80%", d: "1.3s", dur: "7s" },
].map((d, i) => (
<div
key={i}
className="lg-glitter"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: d.t,
left: d.l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
{/* Submit */} {/* Stars */}
<button {[
className="lg-btn" { t: "8%", l: "16%", s: "1.1rem", d: "0s" },
onClick={handleSubmit} { t: "22%", l: "74%", s: "0.85rem", d: "0.7s" },
disabled={isLoading || !email || !password} { t: "55%", l: "20%", s: "0.95rem", d: "1.4s" },
{ t: "86%", l: "58%", s: "0.75rem", d: "0.3s" },
{ t: "44%", l: "88%", s: "1rem", d: "1.0s" },
].map((s, i) => (
<span
key={i}
className="lg-star"
style={
{
top: s.t,
left: s.l,
fontSize: s.s,
animationDelay: s.d,
} as React.CSSProperties
}
> >
{isLoading ? (
<> </span>
<Loader2 size={18} className="lg-spinner" /> Signing in... ))}
</>
) : (
"Sign In →"
)}
</button>
</div>
<p className="lg-footer"> {/* Panel content */}
By signing in you agree to Edbridge's Terms & Privacy Policy. <div className="lg-panel-content">
</p> <div className="lg-panel-logo">
<p className="rg-footer"> <div className="lg-panel-logo-badge">📚</div>
Don't have an account?{" "} <span className="lg-panel-logo-text">EdBridge</span>
<a href="/register" className="rg-link"> </div>
Sign up
</a> <div className="lg-panel-headline">
</p> <h2>
Welcome
<br />
back,
<br />
<span>champion.</span>
</h2>
<p>
Your SAT goals are waiting.
<br />
Pick up right where you left off.
</p>
</div>
<div className="lg-stats">
{[
{
icon: <Target size={14} color="#fff" />,
bg: "linear-gradient(135deg,#3b82f6,#1d4ed8)",
val: "94%",
label: "Accuracy",
},
{
icon: <Clock size={14} color="#fff" />,
bg: "linear-gradient(135deg,#f97316,#ef4444)",
val: "47m",
label: "Daily study",
},
{
icon: <BarChart2 size={14} color="#fff" />,
bg: "linear-gradient(135deg,#22c55e,#15803d)",
val: "+180",
label: "Score gain",
},
].map((s, i) => (
<div className="lg-stat" key={i}>
<div className="lg-stat-icon" style={{ background: s.bg }}>
{s.icon}
</div>
<strong>{s.val}</strong>
<span>{s.label}</span>
</div>
))}
</div>
<div className="lg-social">
<div className="lg-avs">
{AV_INITIALS.map((init, i) => (
<div
key={i}
className="lg-av"
style={{ background: AV_COLORS[i] }}
>
{init}
</div>
))}
</div>
<p>
<strong>2,400+</strong> students improved their scores
</p>
</div>
</div>
</div>
{/* ── RIGHT PANEL ── */}
<div className="lg-right">
{[
{ s: 200, c: "#f97316", t: "4%", r: "4%", d: "0s", dur: "12s" },
{ s: 120, c: "#3b82f6", b: "8%", l: "2%", d: "1.5s", dur: "10s" },
{ s: 70, c: "#22c55e", t: "52%", r: "2%", d: "0.8s", dur: "8s" },
].map((d, i) => (
<div
key={i}
className="lg-bg-dot"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: (d as any).t,
right: (d as any).r,
bottom: (d as any).b,
left: (d as any).l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
<div className="lg-form-wrap">
<div className="lg-form-header">
<h1>Welcome back 👋</h1>
<p>Sign in to continue your SAT prep</p>
</div>
<div className="lg-fields">
<div className="lg-field">
<label className="lg-label" htmlFor="email">
Email
</label>
<div className="lg-input-wrap">
<Mail size={15} className="lg-input-icon" />
<input
id="email"
type="email"
className="lg-input"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-field">
<label className="lg-label" htmlFor="password">
Password
</label>
<div className="lg-input-wrap">
<Lock size={15} className="lg-input-icon" />
<input
id="password"
type="password"
className="lg-input"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-remember">
<input id="rememberMe" type="checkbox" className="lg-checkbox" />
<label htmlFor="rememberMe" className="lg-remember-label">
Keep me signed in
</label>
</div>
{error && (
<div className="lg-error">
<span></span> {error}
</div>
)}
<button
className="lg-btn"
onClick={handleSubmit}
disabled={isLoading || !email || !password}
>
{isLoading ? (
<>
<Loader2 size={18} className="lg-spinner" /> Signing in...
</>
) : (
"Sign In →"
)}
</button>
</div>
<p className="lg-footer">
By signing in you agree to EdBridge's Terms & Privacy Policy.
</p>
<p className="lg-signup-footer">
Don't have an account?{" "}
<span className="lg-link" onClick={() => navigate("/register")}>
Sign up
</span>
</p>
</div>
</div> </div>
</div> </div>
); );

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { Outlet, NavLink, useLocation } from "react-router-dom"; import { Outlet, NavLink, useLocation } from "react-router-dom";
import { Home, BookOpen, Award, User, Video, Map } from "lucide-react"; import { Home, BookOpen, Award, User, Map, SquareLibrary } from "lucide-react";
import { SidebarProvider } from "../../components/ui/sidebar"; import { SidebarProvider } from "../../components/ui/sidebar";
import { AppSidebar } from "../../components/AppSidebar"; import { AppSidebar } from "../../components/AppSidebar";
@ -27,7 +27,7 @@ const NAV_ITEMS = [
}, },
{ {
to: "/student/lessons", to: "/student/lessons",
icon: Video, icon: SquareLibrary,
label: "Lessons", label: "Lessons",
color: "#0891b2", color: "#0891b2",
bg: "rgba(8,145,178,0.12)", bg: "rgba(8,145,178,0.12)",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,773 @@
import React, { useRef, useState, useEffect } from "react";
import { Check, BookOpen, Lightbulb, Zap, Target } from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
CENTRAL_IDEAS_EASY,
CENTRAL_IDEAS_MEDIUM,
} from "../../../data/rw/central-ideas-details";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
/* ── Data for RevealCardGrid widgets ── */
const WRONG_ANSWER_TAXONOMY: RevealCard[] = [
{
label: "Off-topic",
sublabel: "Is this noun/idea actually in the lines?",
content:
"Mentions something never in the passage or unrelated to the topic.",
},
{
label: "Too broad",
sublabel: "Does answer scope match passage scope?",
content:
'Passage focuses on ONE person but answer says "scientists" or "artists" (plural).',
},
{
label: "Too narrow",
sublabel: "Is this the MAIN thing or just one example?",
content: "Picks a supporting detail instead of the overall idea.",
},
{
label: "Half-right, half-wrong",
sublabel: "Read every word in the answer.",
content:
"First half matches, second half contains a false or unsupported claim.",
},
{
label: "Could-be-true",
sublabel: "Can I point to specific words?",
content:
"Plausible in the real world, but not stated or implied in the text.",
},
{
label: "Wrong scope for purpose",
sublabel: "Focus on what the WHOLE passage does.",
content: "Describes what the passage mentions but not its primary goal.",
},
];
const STRUCTURE_PATTERNS: RevealCard[] = [
{
label: "Old Idea → New Idea",
content: "Challenge/revision: most common in science passages",
},
{
label: "Problem → Solution",
content: "A challenge is identified, then an approach is described",
},
{
label: "Claim → Supporting Evidence",
content: "The main point is stated upfront, followed by examples",
},
{
label: "Description → Implication",
content: "A scenario is described, then its significance is analyzed",
},
{
label: "Comparison / Contrast",
content: "Two entities or views are presented side by side",
},
];
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question: "Which sentence states the central idea of this passage?",
passage: [
"For decades, the standard treatment for depression has been antidepressant medication combined with talk therapy.",
"These approaches help many patients, but roughly one-third do not respond to first-line treatments.",
"Researchers are now investigating ketamine, an anesthetic, as a rapid-acting antidepressant.",
"Unlike traditional medications that take weeks to work, ketamine can reduce depressive symptoms within hours.",
"This suggests that the neuroscience of depression is far more complex — and far more treatable — than previously assumed.",
],
evidenceIndex: 4,
explanation:
'Sentence 5 is the "So What" — it draws a broader conclusion about what ketamine research implies about depression science. It\'s the central idea because it states what the author wants us to take away from all the preceding information.',
},
{
question:
"Which sentence best expresses the main point the author wants the reader to understand?",
passage: [
"Ancient Rome is often praised for its engineering feats: aqueducts, roads, and amphitheaters.",
"These structures have survived millennia and continue to function in some cases today.",
"Less celebrated is Rome's sophisticated financial system, which included credit, interest-bearing loans, and transferable debt.",
"Roman bankers financed trade across the Mediterranean, enabling commerce that would otherwise have been impossible.",
"The financial innovations of Rome were as consequential as its physical ones, yet history has largely ignored them.",
],
evidenceIndex: 4,
explanation:
'Sentence 5 is the thesis — the author\'s main argument that Roman financial innovation was equally important to physical engineering. The word "yet" signals this is the key contrast and the point the author most wants to make.',
},
];
const EBRWCentralIdeasLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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="Topic &amp; Main Point"
icon={BookOpen}
/>
<SectionMarker
index={1}
title="Old/New &amp; Structure"
icon={Target}
/>
<SectionMarker
index={2}
title="Pronouns &amp; Compression"
icon={Lightbulb}
/>
<SectionMarker index={3} title="Main Point Hunter" icon={Target} />
<SectionMarker index={4} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* Section 0 — Topic & Main Point */}
<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 w-fit">
Information &amp; Ideas Domain 2
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Central Ideas &amp; Details
</h2>
<p className="text-lg text-slate-500 mb-8">
Identify the main point, central claim, and overall structure and
eliminate every wrong answer type.
</p>
{/* 4A — Identifying the Topic */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
<h3 className="text-lg font-bold text-teal-900">
Identifying the Topic
</h3>
<p className="text-sm text-slate-700">
The topic is the person, thing, or idea that is the primary
subject of the passage. Correct answers to main idea questions
must reference the topic wrong answers often shift to a related
but different subject.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-700 text-sm mb-2">
Key Principles
</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>
The topic appears in sentence 12 in nearly every SAT
passage.
</li>
<li>
A topic is NOT the same as the theme. Topic = subject;
Theme = abstract lesson.
</li>
<li>
Recognize restatements: a computer "the machine," "the
invention," "the technology."
</li>
<li>
The topic word usually recurs throughout by name or via
pronoun/compression noun.
</li>
<li>
Off-topic answers are wrong even if every other word
matches the passage.
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-red-100 rounded-xl p-4">
<p className="font-bold text-red-700 text-sm mb-2">
Common Mistakes
</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>
Naming a category instead of the specific topic (e.g.,
"Japanese art" instead of "Otagaki Rengetsu's art").
</li>
<li>
Confusing a supporting detail mentioned in one sentence
for the main topic.
</li>
<li>
Missing pronoun shifts: "it" may change referent
mid-passage.
</li>
<li>
Selecting an answer that is too broad in scope the
passage discusses ONE scientist, not all scientists.
</li>
</ul>
</div>
</div>
</div>
{/* 4B — Main Point Formula */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
The Main Point Formula
</h3>
<p className="text-sm text-slate-700">
The main point answers the question: "So what?" It is the primary
argument the author wants to convey not just a description of
what was discussed.
</p>
<div className="scroll-reveal-scale bg-teal-900 text-white rounded-xl p-5 text-center">
<p className="text-xl font-extrabold tracking-wide">
Topic + So What? = Main Point
</p>
</div>
{/* Worked Example — Otagaki Rengetsu */}
<div className="bg-teal-50 border border-teal-200 rounded-xl p-5 space-y-3">
<p className="font-bold text-teal-800 text-sm uppercase tracking-wider">
Worked Example
</p>
<div className="bg-white border border-slate-200 rounded-lg p-4 text-sm text-slate-700 leading-relaxed italic">
"Admired primarily for her exquisite calligraphy,{" "}
<span className="font-bold text-teal-700 not-italic">
Otagaki Rengetsu
</span>{" "}
(17911875) was among Japan's most celebrated artists. She was
also a writer and ceramicist, often inscribing{" "}
<span className="underline decoration-teal-400">her poems</span>{" "}
in{" "}
<span className="underline decoration-teal-400">
her own calligraphy
</span>{" "}
onto clay vessels —{" "}
<span className="font-bold text-teal-700 not-italic">
a distinctive blending of art forms not replicated by any
other artist in Japanese history
</span>
.{" "}
<span className="underline decoration-teal-400">Her work</span>{" "}
was in such great demand during the nineteenth century that
every household in Kyoto was said to own{" "}
<span className="underline decoration-teal-400">
her pottery
</span>
, and today{" "}
<span className="underline decoration-teal-400">
scrolls and ceramics
</span>{" "}
bearing{" "}
<span className="underline decoration-teal-400">
her calligraphy
</span>{" "}
are sought after by collectors."
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="card-tilt bg-white border border-teal-200 rounded-lg p-3 text-center">
<p className="text-xs text-slate-500 uppercase tracking-wider mb-1">
Topic
</p>
<p className="font-bold text-teal-800 text-sm">
Otagaki Rengetsu's art
</p>
</div>
<div className="card-tilt bg-white border border-teal-200 rounded-lg p-3 text-center">
<p className="text-xs text-slate-500 uppercase tracking-wider mb-1">
So What?
</p>
<p className="font-bold text-teal-800 text-sm">
Unique, unreplicated qualities
</p>
</div>
<div className="card-tilt bg-teal-700 text-white rounded-lg p-3 text-center">
<p className="text-xs text-teal-200 uppercase tracking-wider mb-1">
Main Point
</p>
<p className="font-bold text-sm">
Her artistic creations are prized for their unique qualities
</p>
</div>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
<p className="text-xs text-slate-700">
<span className="font-bold text-amber-800">
Notice the topic tracking:
</span>{" "}
The topic "Otagaki Rengetsu's art" is introduced in sentence 1
and then restated in other words throughout —{" "}
<em>
her poems, her own calligraphy, her work, her pottery,
scrolls and ceramics, her calligraphy
</em>
. The topic word recurs by name or via pronoun/compression
noun. Every correct answer must reference this specific topic,
not a broader category like "Japanese art."
</p>
</div>
</div>
<p className="text-sm text-slate-600 font-semibold">
Key locations where the main point is typically found:
</p>
<ul className="space-y-1 text-sm text-slate-700">
<li>
•{" "}
<span className="font-bold">First or first two sentences</span>{" "}
— most common.
</li>
<li>
• <span className="font-bold">Last sentence</span> — especially
when the passage confirms or reasserts the opening claim.
</li>
<li>
• <span className="font-bold">After a major transition</span>{" "}
such as however, but, in fact, therefore — the "new idea" is
often the real main point.
</li>
<li>
•{" "}
<span className="font-bold">
After a dash, colon, or italicized word
</span>{" "}
— these signal that something important follows.
</li>
</ul>
</div>
{/* Fiction & Poetry */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-slate-50 border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Fiction &amp; Poetry: Special Cases
</h3>
<div className="space-y-3">
<div>
<p className="font-bold text-teal-700 text-sm mb-1">
Fiction Passages
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
• Focus on who the character is and what quality or feeling
the passage emphasizes.
</li>
<li>
• Key information often appears at the end (a quoted line of
dialogue, a narrator's summary, or a character's
reflection).
</li>
<li>
• DO NOT read in symbolism or broader themes beyond what the
text literally states.
</li>
<li>
• Wrong answers often import an emotion or quality from
outside the text, or conflate a detail with the main idea.
</li>
<li className="italic text-slate-400">
Example: Amy Tan passage about ink-making → main point =
characters take great pride in their generational ink-making
tradition. NOT "the importance of family" (too abstract).
</li>
</ul>
</div>
<div>
<p className="font-bold text-teal-700 text-sm mb-1">
Poetry Passages
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
• Read the poem literally first — what is the speaker
literally saying?
</li>
<li>
• Identify the speaker's attitude (positive, negative,
ambivalent) — this almost always determines the main point.
</li>
<li>
• Look at the last stanza or final couplet for the poem's
culminating idea.
</li>
<li>
• Avoid symbolic overreach: "the winter represents death" is
interpretation, not literal reading.
</li>
<li>
• Process of elimination is very powerful: eliminate any
answer that is either negative when the poem is positive, or
that mentions something never stated.
</li>
</ul>
</div>
</div>
</div>
{/* 4F — Primary Purpose vs. Main Point */}
<div className="scroll-reveal stagger-4 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
<h3 className="text-lg font-bold text-teal-900">
Primary Purpose vs. Main Point
</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-teal-600 text-white">
<th className="px-4 py-2 text-left"></th>
<th className="px-4 py-2 text-left">Main Point</th>
<th className="px-4 py-2 text-left">Primary Purpose</th>
</tr>
</thead>
<tbody>
{[
[
"Question asked",
"What does the author claim?",
"Why did the author write this?",
],
[
"Answer uses",
'Specific nouns, claims, findings ("dark matter cannot be seen but must exist")',
"Function verbs: describe, argue, challenge, illustrate, explain, contrast, suggest",
],
[
"Example",
'"Octavia Butler resisted being identified exclusively with science fiction."',
'"To present a claim and support it with examples."',
],
].map(([label, mp, pp], i) => (
<tr
key={label}
className={i % 2 === 0 ? "bg-white" : "bg-teal-50"}
>
<td className="px-4 py-2 font-bold text-slate-700 text-xs">
{label}
</td>
<td className="px-4 py-2 text-slate-600 text-xs">{mp}</td>
<td className="px-4 py-2 text-slate-600 text-xs">{pp}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Wrong Answer Taxonomy */}
<div className="scroll-reveal stagger-5 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Wrong Answer Taxonomy — tap to reveal each trap:
</h3>
<RevealCardGrid
cards={WRONG_ANSWER_TAXONOMY}
columns={3}
accentColor="teal"
/>
</div>
</section>
{/* Section 1 — Old/New & Structure */}
<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-2">
Old/New &amp; Structure
</h2>
<p className="text-lg text-slate-500 mb-8">
The most important structural pattern on the SAT — and how to
describe overall passage organization.
</p>
{/* 4C — Old/New */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
<h3 className="text-lg font-bold text-teal-900">
Old Idea vs. New Idea Structure
</h3>
<p className="text-sm text-slate-700">
The Old/New template is one of the most important patterns in SAT
science and social science passages. Authors present a
traditionally held view (old idea), then pivot to a new or
contradictory finding.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-red-700 text-sm mb-2">
Old Idea Signal Phrases
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>• "Some/many/most scientists believe..."</li>
<li>• "It is commonly thought that..."</li>
<li>• "Accepted/conventional wisdom holds..."</li>
<li>• "For decades, researchers thought..."</li>
<li>• "Traditionally, it was believed..."</li>
</ul>
<p className="text-xs text-red-600 mt-2 italic">
→ These phrases signal a view the author DISAGREES with.
</p>
</div>
<div className="card-tilt bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-green-700 text-sm mb-2">
New Idea Signal Phrases
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>• "However, but in fact..."</li>
<li>• "Actually, in reality..."</li>
<li>• "But is it really true that...?"</li>
<li>• "It now seems / researchers now think..."</li>
<li>• "Recently, it has been found that..."</li>
<li>• "New research/evidence shows..."</li>
</ul>
<p className="text-xs text-green-600 mt-2 italic">
→ These phrases signal the view the author AGREES with.
</p>
</div>
</div>
<div className="bg-teal-900 text-white rounded-xl p-4">
<p className="font-bold text-sm mb-1">STRATEGY</p>
<p className="text-xs text-teal-100">
As you read, jot on scratch paper: Old = [3-word summary] | New
= [3-word summary]. The main point is almost always the NEW
idea. If you identify the old idea, you can predict the new idea
before reading it.
</p>
</div>
</div>
{/* 4G — Overall Structure */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Overall Structure of a Text — tap to reveal each pattern:
</h3>
<p className="text-sm text-slate-600">
Structure questions ask how a passage is organized. Identify the
move from one idea to another — not just what is said, but in what
sequence and for what purpose.
</p>
<RevealCardGrid
cards={STRUCTURE_PATTERNS}
columns={3}
accentColor="teal"
/>
<div className="bg-slate-800 text-white rounded-xl p-4">
<p className="font-bold text-sm mb-1">
STRATEGY for structure questions
</p>
<p className="text-xs text-slate-200">
Focus on the first and last sentence. The first usually
introduces the main move; the last usually shows where the
passage ended up. Then check the answer choices for the option
that correctly names both ends.
</p>
</div>
</div>
</section>
{/* Section 2 — Pronouns & Compression Nouns */}
<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-2">
Pronouns &amp; Compression Nouns
</h2>
<p className="text-lg text-slate-500 mb-8">
Failure to track these referents is one of the most common
comprehension errors on the SAT.
</p>
{/* 4H */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
<h3 className="text-lg font-bold text-teal-900">
Tracking Pronouns and Compression Nouns
</h3>
<p className="text-sm text-slate-700">
Pronouns (it, they, this, these) and "compression nouns" (this
phenomenon, the former, such developments) refer to ideas already
stated. Failure to track these referents is one of the most common
comprehension errors on the SAT.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-teal-700 text-sm mb-2">
How to Track Pronouns
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
• Always back up to the previous sentence (or earlier) to
find the referent.
</li>
<li>• Singular pronouns (it, this) → singular noun.</li>
<li>• Plural pronouns (they, these) → plural noun.</li>
<li>
• "The former" → first-mentioned item; "the latter" →
second-mentioned item.
</li>
<li>
• Do NOT start reading at the pronoun; always work backwards
first.
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-teal-700 text-sm mb-2">
Compression Noun Examples
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
• "This enhanced convenience" → refers to several sentences
of prior description.
</li>
<li>
• "This divergence" → refers to genetic differences between
dolphin species.
</li>
<li>
• "Such developments" → refers to a process described in the
prior paragraph.
</li>
<li>
• "This phenomenon" → compresses a multi-sentence
explanation into one noun.
</li>
<li>
• The referent may be 35 lines BEFORE the compression noun.
</li>
</ul>
</div>
</div>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-teal-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-teal-100">
The main point is NOT the first sentence — it is the "So What"
conclusion. Read the whole passage, then ask: "If I had to explain
in one sentence what the author WANTS me to believe, what would I
say?" That sentence is the main point. For Old/New passages: the
NEW idea is almost always the main point.
</p>
</div>
</section>
{/* Section 3 — Main Point Hunter widget */}
<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-2">
Main Point Hunter
</h2>
<p className="text-lg text-slate-500 mb-8">
Find the sentence that states the central idea — the "So What"
conclusion.
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="teal"
/>
</section>
{/* Section 4 — Practice */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Questions
</h2>
{CENTRAL_IDEAS_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="teal" />
))}
{CENTRAL_IDEAS_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 EBRWCentralIdeasLesson;

View File

@ -0,0 +1,624 @@
import React, { useRef, useState, useEffect } from "react";
import { Check, BookOpen, BarChart3, Zap, Target } from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
COMMAND_EVIDENCE_EASY,
COMMAND_EVIDENCE_MEDIUM,
} from "../../../data/rw/command-of-evidence";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
/* ── Data for RevealCardGrid widgets ── */
const ILLUSTRATION_TRAPS: RevealCard[] = [
{
label: "Wrong speaker",
content:
"The quotation features the correct idea but from a different character.",
},
{
label: "Right topic, wrong direction",
content:
"The quotation mentions the topic but doesn't illustrate the specific claim.",
},
{
label: "Too indirect",
content:
"The connection between quotation and claim requires too much inferential leaping.",
},
{
label: "Question marks",
content: 'A rhetorical question often cannot "illustrate" a direct claim.',
},
];
const VALIDITY_TYPES: RevealCard[] = [
{
label: "Valid / Necessary",
sublabel: "CORRECT on SAT",
content:
"Must be true given the evidence; the only logical conclusion. Example: If 14% are supershear events → 86% are not.",
},
{
label: "Possible / Speculative",
sublabel: "WRONG on SAT",
content:
'Might be true but the evidence doesn\'t require it. Example: "Researchers must want more funding" — not stated.',
},
{
label: "Contradicted",
sublabel: "WRONG on SAT",
content:
'Directly conflicts with information stated in the passage. Example: "Exercise improves fitness equally for all" — passage says otherwise.',
},
{
label: "Off-topic",
sublabel: "WRONG on SAT",
content:
"No logical connection to the claim or evidence. Example: Ocean temperature claim when passage is about land volcanoes.",
},
];
const QUANT_WRONG_ANSWERS: RevealCard[] = [
{
label: "Wrong subgroup / time period",
content: "Accurate data about the WRONG subgroup or time period.",
},
{
label: "Wrong direction",
content:
"Accurate comparison in the WRONG direction (A > B when claim needs B > A).",
},
{
label: "Wrong number of groups",
content: "Involves TWO groups when the claim is about ONE group only.",
},
{
label: "Contradictory trend",
content:
"Describes a trend that contradicts the claim despite accurate numbers.",
},
{
label: "Right data, wrong claim",
content:
"Describes the graph accurately but doesn't address the specific claim.",
},
];
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question:
"The researcher concludes that urban green spaces reduce stress. Which sentence from the study best SUPPORTS this conclusion?",
passage: [
"Participants were randomly assigned to walk for 30 minutes in either an urban park or a busy commercial district.",
"Before and after each walk, cortisol levels were measured using saliva samples.",
"Participants who walked in the park showed a 15% reduction in cortisol, a primary stress hormone.",
"Those who walked in the commercial district showed no significant change in cortisol levels.",
"Participants reported feeling calmer after the park walk, though self-report data is inherently subjective.",
],
evidenceIndex: 2,
explanation:
"Sentence 3 provides direct biological evidence (cortisol reduction) that supports the claim about stress reduction. It uses objective measurement rather than self-report, making it the strongest support for the stated conclusion.",
},
{
question:
"Which sentence from this passage most effectively ILLUSTRATES the claim that microplastics are now found in unexpected locations?",
passage: [
"Microplastics are plastic fragments smaller than 5 millimeters.",
"They originate from the breakdown of larger plastic items or are manufactured at microscopic size.",
"Researchers have detected microplastics in the peak snowpack of Mount Everest.",
"Microplastics have also been found in human blood, lung tissue, and placentas.",
"The long-term health effects of microplastic exposure are still being studied.",
],
evidenceIndex: 2,
explanation:
"Sentence 3 best illustrates the claim about unexpected locations because Mount Everest is one of the most remote places on Earth — finding microplastics there is a striking, concrete example of how pervasive contamination has become.",
},
];
const EBRWCommandEvidenceLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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="Textual Evidence" icon={BookOpen} />
<SectionMarker
index={1}
title="Quantitative Evidence"
icon={BarChart3}
/>
<SectionMarker index={2} title="Evidence Hunter" icon={Target} />
<SectionMarker index={3} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* Section 0 — Textual Evidence */}
<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 w-fit">
Information &amp; Ideas Domain 2
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Command of Evidence
</h2>
<p className="text-lg text-slate-500 mb-8">
Move beyond the passage to apply its ideas. Two subtypes: Textual
Evidence (quotations) and Quantitative Evidence (graphs and tables).
</p>
<div className="scroll-reveal stagger-1 bg-teal-50 border border-teal-200 rounded-2xl p-5 mb-8">
<p className="text-sm text-slate-700">
<span className="font-bold text-teal-800">Overview: </span>Command
of Evidence questions ask you to move BEYOND the passage to apply
its ideas. You will identify quotations or data that illustrate,
support, or undermine a specific claim. There are two main
subtypes: Textual Evidence (using quotations or passages) and
Quantitative Evidence (using graphs and tables).
</p>
</div>
{/* 5A */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Illustrating a Claim (Quotation Selection)
</h3>
<p className="text-sm text-slate-600">
These questions ask you to find the quotation from a poem, story,
or passage that best illustrates a claim stated in the question
stem. The claim is explicitly given to you your job is to match
it to the correct quotation.
</p>
<div className="bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
3-Step Process for Illustration Questions
</p>
<div className="space-y-2">
{[
[
"1",
"RESTATE the claim in the question stem in your own words. Identify the exact quality or action it describes.",
],
[
"2",
"PREDICT what kind of language would illustrate it — positive/negative tone, specific action, direct statement?",
],
[
"3",
"ELIMINATE quotations that: (a) are too vague, (b) refer to the wrong speaker, (c) describe a different quality entirely.",
],
].map(([n, text]) => (
<div key={n} className="flex gap-2">
<span className="w-5 h-5 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{n}
</span>
<p className="text-xs text-slate-700">{text}</p>
</div>
))}
</div>
</div>
<p className="font-semibold text-sm text-slate-800">
Key traps in illustration questions tap to reveal:
</p>
<RevealCardGrid
cards={ILLUSTRATION_TRAPS}
columns={2}
accentColor="teal"
/>
</div>
{/* 5B */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Supporting a Claim
</h3>
<p className="text-sm text-slate-600">
Support questions ask: "Which finding would MOST DIRECTLY support
this conclusion?" The correct answer must provide new evidence
consistent with the claim it doesn't just repeat what the
passage already states.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-green-50 border border-green-200 rounded-xl p-4">
<p className="font-bold text-green-800 text-sm mb-2">
What Makes a Valid Support?
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
• Provides a NEW example or finding, not a restatement.
</li>
<li>
• Is directly consistent with the specific mechanism
described.
</li>
<li>• Makes the claim MORE likely to be true.</li>
<li>
• Common patterns: X causes Y → new example of X causing Y;
More X → more Y → find a case where less X → less Y.
</li>
</ul>
</div>
<div className="card-tilt bg-red-50 border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-2">
What Looks Like Support But Isn't
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
The answer discusses the right topic but a different
aspect of it.
</li>
<li>
The answer is consistent with the general field but not
the specific claim.
</li>
<li>
The answer only restates part of what the passage already
said.
</li>
<li>
The answer is factually true but would also be true
regardless of the claim.
</li>
</ul>
</div>
</div>
</div>
{/* 5C */}
<div className="scroll-reveal stagger-4 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Undermining a Claim
</h3>
<p className="text-sm text-slate-600">
Undermine questions have the same structure as support questions,
but in reverse. The correct answer must provide information that
makes the claim LESS likely to be true.
</p>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-1">
KEY TECHNIQUE Flip the Claim
</p>
<p className="text-xs text-slate-700">
If the claim is "high metabolic rate = survival advantage," then
to undermine it you need evidence that high metabolic rate does
NOT produce survival advantage (e.g., many high-metabolic
creatures went extinct).
</p>
</div>
<p className="font-semibold text-sm text-slate-800">
Common undermine traps:
</p>
{[
"The answer is unrelated to the claim rather than contradictory to it — an unrelated finding doesn't undermine anything.",
"The answer challenges a secondary detail, not the core mechanism being tested.",
"The answer actually supports the claim but is framed in negative-sounding language.",
].map((trap, i) => (
<div
key={i}
className="flex gap-2 bg-red-50 border border-red-100 rounded-lg px-3 py-2"
>
<span className="text-red-500 font-bold shrink-0 text-xs">
</span>
<p className="text-xs text-slate-600">{trap}</p>
</div>
))}
</div>
{/* 5D */}
<div className="scroll-reveal stagger-5 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Validity of Conclusions tap to reveal each type:
</h3>
<p className="text-sm text-slate-600">
Some questions ask whether a finding is valid whether it
necessarily follows from the research described.
</p>
<RevealCardGrid
cards={VALIDITY_TYPES}
columns={2}
accentColor="teal"
/>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-teal-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule Textual Evidence</p>
<p className="text-sm text-teal-100">
The question always tells you the required relationship
(illustrate / support / undermine). An answer that accurately
quotes the passage but has the WRONG relationship is still wrong.
Identify the relationship first, accuracy second.
</p>
</div>
</section>
{/* Section 1 — Quantitative Evidence */}
<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-2">
Quantitative Evidence
</h2>
<p className="text-lg text-slate-500 mb-8">
Graphs and tables the mandatory order of operations and criteria
matching technique.
</p>
{/* 5E */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-amber-50 border border-amber-200 space-y-4">
<h3 className="text-lg font-bold text-amber-900">
Non-Negotiable Order of Operations
</h3>
<p className="text-sm text-slate-700">
The most important principle:{" "}
<strong>the graphic alone is never sufficient.</strong> You must
begin with the passage and the question to know what to look for
in the graphic.
</p>
<div className="bg-white border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-3">
MANDATORY Order for Graph Questions
</p>
<div className="space-y-2">
{[
[
"1",
"Read the PASSAGE (especially the last sentence — this states the claim).",
],
[
"2",
"Read the QUESTION STEM carefully to identify exactly what you are being asked.",
],
[
"3",
"Extract the CRITERIA from the claim: what specific conditions must be met?",
],
[
"4",
"THEN look at the GRAPHIC with those criteria in mind.",
],
[
"5",
"Match the answer that satisfies ALL criteria — not just part of them.",
],
].map(([n, text]) => (
<div key={n} className="flex gap-2">
<span className="w-5 h-5 rounded-full bg-amber-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{n}
</span>
<p className="text-xs text-slate-700">{text}</p>
</div>
))}
</div>
</div>
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-1">
CRITICAL WARNING
</p>
<p className="text-xs text-slate-700">
Looking at the graph first is one of the most costly errors on
graph questions. Multiple answer choices will accurately
describe the graph only ONE will match the specific claim in
the passage. The graphic alone cannot tell you which one is
correct.
</p>
</div>
</div>
{/* 5F */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-3">
<h3 className="text-lg font-bold text-slate-900">
When You Do NOT Need the Graph
</h3>
<p className="text-sm text-slate-600">
Many graph questions can be answered using only the passage and
the answer choices without looking at the graph at all.
</p>
<ul className="space-y-2 text-sm text-slate-700">
<li className="flex gap-2">
<span className="text-teal-600 font-bold shrink-0"></span>If
answer choices contain wording clearly inconsistent with the
passage's claim, eliminate them immediately.
</li>
<li className="flex gap-2">
<span className="text-teal-600 font-bold shrink-0"></span>
Answers addressing the wrong aspect of the claim (wrong time
period, wrong variable, wrong group) can be eliminated before
consulting the graph.
</li>
<li className="flex gap-2">
<span className="text-teal-600 font-bold shrink-0"></span>Once
only one answer remains that is consistent with the claim, that
is correct verifying against the graph is optional.
</li>
</ul>
<div className="bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-xs mb-1">EXAMPLE</p>
<p className="text-xs text-slate-700">
Passage claims "print books are preferred in certain
situations." Any answer describing a situation where e-books are
preferred can be immediately eliminated without looking at the
chart.
</p>
</div>
</div>
{/* 5G */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Criteria Matching for Quantitative Questions
</h3>
<p className="text-sm text-slate-600">
The most common error: choosing an answer that accurately
describes the graph but fails to match ALL criteria specified in
the claim. Build a checklist before looking at the data.
</p>
<div className="card-tilt bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
Building a Criteria Checklist
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li> List every specific condition mentioned in the claim.</li>
<li>
Example: "rebounded AND reached highest level in 60 years" =
2 separate criteria.
</li>
<li>
An answer meeting only Criterion 1 but not Criterion 2 is
wrong, even if it accurately describes the graph.
</li>
<li>
Write criteria on scratch paper before looking at graphic.
</li>
</ul>
</div>
<p className="font-semibold text-sm text-slate-800">
Common Quantitative Wrong Answers tap to reveal:
</p>
<RevealCardGrid
cards={QUANT_WRONG_ANSWERS}
columns={3}
accentColor="teal"
/>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-teal-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">
Golden Rule Quantitative Evidence
</p>
<p className="text-sm text-teal-100">
Always read the passage and question first to extract criteria.
Multiple answer choices will accurately describe the graph only
ONE matches the specific claim. "Right data, wrong claim aspect"
is the most common wrong answer type.
</p>
</div>
</section>
{/* Section 2 — Evidence Hunter widget */}
<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-2">
Evidence Hunter
</h2>
<p className="text-lg text-slate-500 mb-8">
Find the sentence that has the exact relationship the question
requires.
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="teal"
/>
</section>
{/* Section 3 — Practice */}
<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">
Practice Questions
</h2>
{COMMAND_EVIDENCE_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="teal" />
))}
{COMMAND_EVIDENCE_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 EBRWCommandEvidenceLesson;

View File

@ -239,7 +239,7 @@ const EBRWCommasLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Clause Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Clause Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -117,7 +117,7 @@ const EBRWCraftStructureLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Craft & Purpose" icon={BookOpen} /> <SectionMarker index={0} title="Craft & Purpose" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -0,0 +1,496 @@
import React, { useRef, useState, useEffect } from "react";
import { Check, BookOpen, Lightbulb, Zap, Target } from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
CROSS_TEXT_EASY,
CROSS_TEXT_MEDIUM,
} from "../../../data/rw/cross-text-connections";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
/* ── Data for RevealCardGrid widgets ── */
const RESPONSE_PATTERNS: RevealCard[] = [
{
label: "Overstated / exaggerated",
content: '"It overstates the environmental impact of X."',
},
{
label: "Insufficiently supported",
content: '"The data do not support the conclusion drawn."',
},
{
label: "Incomplete / overlooks evidence",
content: '"It overlooks a crucial statistic / factor."',
},
{
label: "Logically flawed",
content: '"The reasoning does not follow from the evidence presented."',
},
];
const SCOPE_ERRORS: RevealCard[] = [
{
label: "Scope Error",
content:
"Answer is more general or more specific than what either text actually argues. The correct answer matches the EXACT scope of the relevant text.",
},
{
label: "One-Text Error",
content:
'Answer accurately reflects one text but ignores the other. In "both agree" questions, every word must apply to both passages.',
},
{
label: "Extreme Language",
content:
'Answer uses "always," "never," "all," "none" when neither author makes such an absolute claim.',
},
{
label: "Fabricated Connection",
content:
"Answer invents a relationship between the texts that neither author states or implies.",
},
];
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question:
"Text 1 claims social media increases political polarization. Which sentence from Text 2 most directly responds to this claim?",
passage: [
"Social media platforms are designed to maximize engagement through outrage.",
"Studies of Twitter and Facebook show users cluster in ideological echo chambers.",
"However, a 2022 meta-analysis of 31 studies found no consistent causal link between social media use and polarization.",
"The authors note that correlation between social media use and polarized views may reflect self-selection bias.",
"People who are already polarized may simply use social media more frequently.",
],
evidenceIndex: 2,
explanation:
'Sentence 3 directly challenges the claim that social media causes polarization by citing a meta-analysis finding no consistent causal link. The word "however" signals this is the point where Text 2 diverges from Text 1\'s argument.',
},
{
question:
"Both passages discuss the role of apprenticeships in education. Which sentence from Text 2 would Text 1's author most likely agree with?",
passage: [
"Modern universities emphasize abstract theory over practical skills.",
"Employers increasingly report that graduates cannot apply classroom knowledge to real problems.",
"Apprenticeship programs combine theoretical learning with supervised hands-on practice.",
"Countries with strong apprenticeship traditions, such as Germany, report lower youth unemployment.",
"Critics argue that apprenticeships limit social mobility by tracking students into specific trades.",
],
evidenceIndex: 2,
explanation:
"Sentence 3 describes apprenticeships as combining theory and practice — this is the bridge both texts share. Text 1 likely argues for integrating practical skills with theoretical knowledge, making this the point of agreement.",
},
];
const EBRWCrossTextLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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-fuchsia-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
${isActive ? "bg-fuchsia-600 text-white" : isPast ? "bg-fuchsia-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-fuchsia-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="Paired Passage Strategy"
icon={BookOpen}
/>
<SectionMarker
index={1}
title="Relationship Types"
icon={Lightbulb}
/>
<SectionMarker index={2} title="Connection Finder" icon={Target} />
<SectionMarker index={3} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* ── SECTION 0: PAIRED PASSAGE STRATEGY ── */}
<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-fuchsia-100 text-fuchsia-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4 w-fit">
Craft &amp; Structure Domain 1
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Cross-Text Connections
</h2>
<p className="text-lg text-slate-500 mb-8">
Two short passages, one question always about the relationship
between the authors' claims. Summarize each text before reading the
question.
</p>
{/* Format & Question Stems */}
<div className="scroll-reveal stagger-1 bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4">
<h3 className="text-lg font-bold text-fuchsia-900">
Format &amp; Common Question Stems
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
What to Expect
</p>
<ul className="space-y-1">
{[
"Both texts are very short — typically 50100 words each.",
"They cover the same general topic but present different perspectives.",
"Only one question accompanies the pair.",
"The most common relationship is disagreement — one challenges or qualifies the other.",
"Authors may also partially agree, or one may complicate the other's conclusion.",
].map((f) => (
<li
key={f}
className="flex items-start gap-2 text-xs text-slate-700"
>
<span className="text-fuchsia-500 shrink-0 mt-0.5">
</span>
{f}
</li>
))}
</ul>
</div>
<div className="card-tilt bg-white border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Common Question Stems
</p>
<ul className="space-y-2">
{[
'"Based on the texts, how would [Author 2] most likely describe the view in Text 1?"',
'"Based on the texts, how would [Author 2] most likely respond to what [Author 1] says about X?"',
'"Both texts discuss X. How do they differ in their treatment of it?"',
'"Which statement would [Author 1] most likely agree with?"',
].map((stem) => (
<li
key={stem}
className="text-xs text-slate-600 italic bg-fuchsia-50 rounded-lg px-2 py-1"
>
{stem}
</li>
))}
</ul>
</div>
</div>
</div>
{/* Five-Step Process */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
The Five-Step Process
</h3>
<div className="space-y-3">
{[
{
step: 1,
action: "Read Text 1 and identify its main point",
tip: "Write it down in 46 words on scratch paper.",
},
{
step: 2,
action: "Read Text 2 and identify its main point",
tip: "Write it down in 46 words on scratch paper.",
},
{
step: 3,
action: "Write the relationship between the two",
tip: "Disagree, partially agree, or agree with different emphasis.",
},
{
step: 4,
action:
"Answer the question in your own words before looking at options",
tip: "Base this on your written main points and the relationship.",
},
{
step: 5,
action:
"Read the answer choices and select the closest match",
tip: "Use elimination wrong charges can often be spotted from the first few words of each option.",
},
].map((s) => (
<div
key={s.step}
className="flex gap-3 bg-fuchsia-50 border border-fuchsia-100 rounded-xl p-4"
>
<span className="w-7 h-7 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-sm font-bold shrink-0">
{s.step}
</span>
<div>
<p className="font-bold text-slate-800 text-sm">
{s.action}
</p>
<p className="text-xs text-slate-500 mt-0.5">{s.tip}</p>
</div>
</div>
))}
</div>
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-1">
Critical Warning
</p>
<p className="text-xs text-slate-700">
The biggest mistake is reading both passages without writing
anything down and relying on memory. Under exam pressure, this
almost always leads to error. The notes take ten seconds and can
save a minute of confusion.
</p>
</div>
</div>
</section>
{/* ── SECTION 1: RELATIONSHIP TYPES & ELIMINATION ── */}
<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-2">
Relationship Types
</h2>
<p className="text-lg text-slate-500 mb-8">
Once the relationship is established, elimination becomes powerful —
wrong-charge answers can be ruled out immediately.
</p>
{/* 3 Relationship Types */}
<div className="scroll-reveal stagger-1 bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4">
<h3 className="text-lg font-bold text-fuchsia-900">
The 3 Relationship Types &amp; How to Eliminate
</h3>
<div className="space-y-3">
{[
{
type: "Disagree",
color: "bg-red-50 border-red-200",
badge: "bg-red-600",
desc: "Authors have conflicting views on the same topic. Text 1 argues X; Text 2 argues the opposite or Not-X.",
elim: 'Eliminate any answer that is positive (e.g., "strongly supported," "generally accurate") or neutral to the point of meaninglessness.',
},
{
type: "Partially Agree",
color: "bg-amber-50 border-amber-200",
badge: "bg-amber-600",
desc: "Authors share common ground on some aspects but diverge on others.",
elim: "Eliminate answers that treat the relationship as purely positive or purely negative the correct answer acknowledges both what they share and where they differ.",
},
{
type: "Agree",
color: "bg-green-50 border-green-200",
badge: "bg-green-600",
desc: "Authors reach the same basic conclusion, though they may approach it differently or emphasize different aspects.",
elim: "Eliminate answers that overstate the disagreement, or that are accurate for one text but not both.",
},
].map((r) => (
<div
key={r.type}
className={`card-tilt border rounded-xl p-4 ${r.color}`}
>
<div className="flex items-center gap-2 mb-2">
<span
className={`px-3 py-0.5 rounded-full text-white text-xs font-bold ${r.badge}`}
>
{r.type}
</span>
</div>
<p className="text-sm text-slate-700 mb-1">{r.desc}</p>
<p className="text-xs text-slate-500 italic">
<span className="font-bold not-italic text-slate-600">
To eliminate:{" "}
</span>
{r.elim}
</p>
</div>
))}
</div>
</div>
{/* Words to Watch + Response Patterns */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Words to Watch &amp; Typical Response Patterns
</h3>
<div className="space-y-2 mb-4">
<p className="text-sm text-slate-600">
These words in answer choices typically signal incorrect
answers:
</p>
{[
[
'"strongly supported," "proven," "definitively"',
"Too positive / too absolute",
],
['"only possible in," "impossible to"', "Too extreme"],
['"highly implausible"', "Usually overstates the disagreement"],
].map(([phrase, note]) => (
<div
key={phrase}
className="flex gap-3 bg-red-50 border border-red-200 rounded-lg px-4 py-2"
>
<span className="text-red-500 font-bold shrink-0">✗</span>
<div>
<p className="text-xs font-bold text-red-800 italic">
{phrase}
</p>
<p className="text-xs text-slate-600">{note}</p>
</div>
</div>
))}
</div>
<p className="text-sm font-semibold text-slate-700 mb-2">
Typical Response Patterns — tap to reveal example phrasing:
</p>
<RevealCardGrid
cards={RESPONSE_PATTERNS}
columns={2}
accentColor="fuchsia"
/>
</div>
{/* Scope & Specificity Errors */}
<div className="scroll-reveal stagger-3 bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4">
<h3 className="text-lg font-bold text-fuchsia-900">
Scope &amp; Specificity Errors — tap to reveal each trap:
</h3>
<RevealCardGrid
cards={SCOPE_ERRORS}
columns={2}
accentColor="fuchsia"
/>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-fuchsia-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-fuchsia-100">
Summarize each text in one sentence BEFORE reading the question.
Write it down — do not rely on memory. The question will always be
about the relationship between those two summaries. If you know
what each author claims and how they relate, the question becomes
straightforward.
</p>
</div>
</section>
{/* ── SECTION 2: WIDGET ── */}
<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-2">
Connection Finder
</h2>
<p className="text-lg text-slate-500 mb-8">
Find the sentence that directly engages with the other text's
argument.
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="fuchsia"
/>
</section>
{/* ── SECTION 3: PRACTICE ── */}
<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">
Practice Questions
</h2>
{CROSS_TEXT_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
{CROSS_TEXT_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-fuchsia-900 text-white font-bold rounded-full hover:bg-fuchsia-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWCrossTextLesson;

View File

@ -225,7 +225,7 @@ const EBRWDashesApostrophesLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker
index={0} index={0}

View File

@ -119,7 +119,7 @@ const EBRWExplicitMeaningLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker
index={0} index={0}

View File

@ -119,7 +119,7 @@ const EBRWExpressionIdeasLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Synthesis & Goals" icon={BookOpen} /> <SectionMarker index={0} title="Synthesis & Goals" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -0,0 +1,940 @@
import React, { useRef, useState, useEffect } from "react";
import {
Check,
BookOpen,
AlertTriangle,
Zap,
Clock,
Users,
GitBranch,
} from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
FORM_STRUCTURE_EASY,
FORM_STRUCTURE_MEDIUM,
} from "../../../data/rw/form-structure-sense";
import DecisionTreeWidget, {
type TreeScenario,
type TreeNode,
} from "../../../components/lessons/DecisionTreeWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
/* ── Data for RevealCardGrid widgets ── */
const SVA_DISGUISES: RevealCard[] = [
{
label: "Prepositional Phrase Decoy",
sublabel: "The list [of requirements] __ long.",
content: 'Cross out the phrase. "List" is singular → IS long.',
},
{
label: "Inverted Order (There is/are)",
sublabel: "There __ many reasons for the decision.",
content:
'"Many reasons" is the subject (plural) → THERE ARE. Flip: "Many reasons there are."',
},
{
label: "Gerund Subject",
sublabel: "Studying the data __ essential.",
content:
"A gerund (-ing phrase) as subject is always singular → IS essential.",
},
{
label: "Collective Nouns",
sublabel: "The committee __ voted unanimously.",
content:
"Committee = one group = singular → HAS voted. Same for team, group, family, audience, class.",
},
{
label: "Indefinite Pronouns",
sublabel: "Each of the participants __ required.",
content: "Each is always singular → IS required.",
},
{
label: "Neither/Nor Proximity",
sublabel: "Neither the principal nor the teachers __",
content:
"Or/nor: agree with the NEAREST noun → teachers (plural) → ARE responsible.",
},
];
const PRONOUN_RULES: RevealCard[] = [
{
label: "Number Agreement",
content:
'Pronoun must match its antecedent in number. "Each student should bring their own materials" is accepted on SAT for singular "they."',
},
{
label: "This/That Must Have a Clear Referent",
content:
'Always make sure "this" or "that" refers to a specific noun, not a vague idea. If unclear, replace with the noun.',
},
{
label: "Emphatic Pronouns",
content:
'These (himself/herself/themselves) can NEVER be the subject. ✗ "Himself was the only one who knew." ✓ "He himself was the only one who knew."',
},
{
label: "Missing Antecedent",
content:
'Every pronoun must refer to a specific named noun. If "they" could refer to two different nouns, replace it with the noun it means.',
},
{
label: "Person Consistency",
content:
'Do not switch between "one," "you," and "we" within the same passage. Match the pronoun person already established.',
},
];
const SVA_TREE: TreeNode = {
id: "sva-root",
question:
"Is there a prepositional phrase or interrupting clause BETWEEN the subject and verb?",
hint: 'Cross out everything introduced by "of," "in," "with," "for," "by," "including," etc., up to the next punctuation.',
yesLabel: "Yes — there is interrupting material",
noLabel: "No — subject and verb are adjacent",
yes: {
id: "cross-out",
question:
"After crossing out the interrupting material, is the true subject singular or plural?",
hint: "The true subject is the noun BEFORE the interrupting phrase. Ignore the noun inside the phrase.",
yesLabel: "Singular subject",
noLabel: "Plural subject",
yes: {
id: "singular-verb",
question: "",
result:
"✓ Use a SINGULAR verb (is, was, has, does). The interrupting phrase cannot change the subject.",
resultType: "correct",
ruleRef: 'The list [of requirements] IS long — not "are"',
},
no: {
id: "plural-verb",
question: "",
result:
"✓ Use a PLURAL verb (are, were, have, do). The interrupting phrase cannot change the subject.",
resultType: "correct",
ruleRef: 'The scientists [in the lab] ARE conducting — not "is"',
},
},
no: {
id: "no-interrupt",
question:
"Is the subject an indefinite pronoun (each, every, either, neither, anyone, everyone, someone, nobody)?",
hint: "These pronouns are always grammatically singular even when they refer to groups.",
yesLabel: "Yes — indefinite pronoun subject",
noLabel: "No — regular noun or pronoun",
yes: {
id: "indefinite",
question: "",
result:
"✓ Use a SINGULAR verb. Each, every, either, neither, anyone, everyone, someone, nobody are all grammatically singular.",
resultType: "correct",
ruleRef: 'Each of the students IS responsible — not "are"',
},
no: {
id: "regular-agree",
question: "",
result:
"✓ Match the verb to the subject normally: singular noun = singular verb; plural noun = plural verb.",
resultType: "info",
ruleRef: "Standard agreement: The dog barks / The dogs bark",
},
},
};
const TREE_SCENARIOS: TreeScenario[] = [
{
label: "SVA 1",
sentence:
"The collection of ancient manuscripts were donated to the university library.",
tree: SVA_TREE,
},
{
label: "SVA 2",
sentence:
"Neither of the proposed solutions address the root cause of the problem.",
tree: SVA_TREE,
},
{
label: "SVA 3",
sentence:
"The team of researchers have published their findings in three separate journals.",
tree: SVA_TREE,
},
];
const EBRWFormStructureSenseLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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-purple-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
${isActive ? "bg-purple-600 text-white" : isPast ? "bg-purple-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-purple-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="Subject-Verb Agreement"
icon={BookOpen}
/>
<SectionMarker
index={1}
title="Verb Forms &amp; Tense"
icon={Clock}
/>
<SectionMarker
index={2}
title="Pronouns &amp; Apostrophes"
icon={Users}
/>
<SectionMarker
index={3}
title="Modifiers &amp; Parallel"
icon={AlertTriangle}
/>
<SectionMarker index={4} title="SVA Decision Tree" icon={GitBranch} />
<SectionMarker index={5} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* ── Section 0: Subject-Verb Agreement ── */}
<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-purple-100 text-purple-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4 w-fit">
Standard English Conventions Domain 3
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Form, Structure &amp; Sense
</h2>
<p className="text-lg text-slate-500 mb-8">
Seven grammar skills: subject-verb agreement, verb forms, pronouns,
apostrophes, modification, parallel structure, and comma usage.
</p>
{/* §8.1 SVA */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-purple-50 border border-purple-200 space-y-4">
<h3 className="text-lg font-bold text-purple-900">
Subject-Verb Agreement: All 6 Disguises
</h3>
<p className="text-sm text-slate-700">
The SAT hides the subject behind phrases and structural tricks.
Tap each disguise to see how to solve it:
</p>
<RevealCardGrid
cards={SVA_DISGUISES}
columns={3}
accentColor="purple"
/>
<div className="bg-white border border-purple-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Indefinite Pronoun Quick Reference
</p>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-purple-100">
<th className="px-3 py-1 text-left text-purple-800">
Always Singular
</th>
<th className="px-3 py-1 text-left text-purple-800">
Always Plural
</th>
<th className="px-3 py-1 text-left text-purple-800">
Context-Dependent
</th>
</tr>
</thead>
<tbody>
<tr className="bg-white">
<td className="px-3 py-2 text-slate-600">
each, every, either, neither, one, anyone, everyone,
someone, no one, nobody, somebody, everybody, anything,
everything, something, nothing
</td>
<td className="px-3 py-2 text-slate-600">
both, few, many, several, others
</td>
<td className="px-3 py-2 text-slate-600">
all, any, more, most, none, some match to the noun in
the "of" phrase: "most of the water IS" / "most of the
results ARE"
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
{/* ── Section 1: Verb Forms & Tense ── */}
<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-2">
Verb Forms &amp; Tense
</h2>
<p className="text-lg text-slate-500 mb-8">
The SAT tests specific verb form errors, not just tense consistency.
</p>
{/* Tense Table */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-purple-50 border border-purple-200 space-y-4">
<h3 className="text-lg font-bold text-purple-900">
Tense Consistency
</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-purple-600 text-white">
<th className="px-3 py-2 text-left text-xs">Tense</th>
<th className="px-3 py-2 text-left text-xs">
Signal Words
</th>
<th className="px-3 py-2 text-left text-xs">Example</th>
</tr>
</thead>
<tbody>
{[
[
"Simple Present",
"now, every day, always, usually, generally",
"The study examines 500 participants.",
],
[
"Simple Past",
"yesterday, last year, in 1990, ago",
"The study examined 500 participants.",
],
[
"Present Perfect",
"since, for, recently, already, yet",
"Researchers have found new evidence.",
],
[
"Past Perfect",
"before, by the time, had already",
"By 2020, scientists had confirmed the theory.",
],
[
"Future",
"tomorrow, next year, will",
"The team will publish next month.",
],
[
"Progressive",
"currently, at the moment, was/is + -ing",
"She was collecting data when the power failed.",
],
].map(([tense, signals, ex], i) => (
<tr
key={tense}
className={i % 2 === 0 ? "bg-white" : "bg-purple-50"}
>
<td className="px-3 py-2 font-bold text-purple-800 text-xs">
{tense}
</td>
<td className="px-3 py-2 text-slate-500 text-xs">
{signals}
</td>
<td className="px-3 py-2 text-slate-700 text-xs italic">
{ex}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Past Conditional */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Past Conditional &amp; Irregular Past Participles
</h3>
<div className="bg-purple-50 border border-purple-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Past Conditional (Counterfactual)
</p>
<p className="text-xs text-slate-600 mb-2">
Use <span className="font-bold">had + past participle</span> in
the "if" clause and{" "}
<span className="font-bold">would have + past participle</span>{" "}
in the result clause.
</p>
<ul className="space-y-1 text-xs">
<li className="text-red-600">
"If she would have studied harder, she would have passed."
</li>
<li className="text-green-700">
"If she <strong>had studied</strong> harder, she{" "}
<strong>would have passed</strong>."
</li>
<li className="text-slate-400 italic mt-1">
The SAT frequently places "would have" in the if-clause
always wrong.
</li>
</ul>
</div>
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-slate-700 text-white">
<th className="px-3 py-2 text-left">Base Form</th>
<th className="px-3 py-2 text-left">Simple Past</th>
<th className="px-3 py-2 text-left">
Past Participle (with have/had)
</th>
</tr>
</thead>
<tbody>
{[
["go", "went", "gone (had gone ✓ / had went ✗)"],
["rise", "rose", "risen (had risen ✓ / had rose ✗)"],
["lie (recline)", "lay", "lain (had lain ✓)"],
["lay (place)", "laid", "laid (had laid ✓)"],
["choose", "chose", "chosen (had chosen ✓)"],
["swim", "swam", "swum (had swum ✓ / had swam ✗)"],
["begin", "began", "begun (had begun ✓ / had began ✗)"],
["see", "saw", "seen (had seen ✓ / had saw ✗)"],
].map(([base, past, participle], i) => (
<tr
key={base}
className={i % 2 === 0 ? "bg-white" : "bg-slate-50"}
>
<td className="px-3 py-2 font-bold text-purple-800">
{base}
</td>
<td className="px-3 py-2 text-slate-600">{past}</td>
<td className="px-3 py-2 text-slate-700">{participle}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-2">
To vs. -ing After Certain Verbs
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs">
{[
{
label: "Verbs that take TO",
words:
"want, hope, plan, decide, need, agree, refuse, offer, expect, seem",
},
{
label: "Verbs that take -ING",
words:
"enjoy, avoid, finish, consider, deny, practice, suggest, admit, keep, quit",
},
{
label: "Meaning changes",
words:
"stop to do (pause) / stop doing (cease); remember to do / remember doing",
},
].map((v) => (
<div key={v.label}>
<p className="font-bold text-amber-700 mb-1">{v.label}</p>
<p className="text-slate-600">{v.words}</p>
</div>
))}
</div>
</div>
<div className="bg-purple-50 border border-purple-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Passive Voice
</p>
<p className="text-xs text-slate-600 mb-3">
Passive voice reverses the usual subject-verb-object order: the
receiver of the action becomes the grammatical subject. The SAT
does not treat passive voice as inherently wrong but it tests
whether you can identify the correct form.
</p>
<ul className="space-y-1 text-xs mb-3">
<li className="text-slate-600">
<span className="font-bold">Active:</span> "The researcher
collected the data."
</li>
<li className="text-slate-600">
<span className="font-bold">Passive:</span> "The data was
collected by the researcher."
</li>
</ul>
<p className="text-xs text-slate-500 italic">
Passive is formed with a form of 'be' + past participle. On the
SAT, check whether the passive form matches the correct tense
and whether the subject-verb agreement is correct in the passive
construction.
</p>
</div>
</div>
</section>
{/* ── Section 2: Pronouns & Apostrophes ── */}
<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-2">
Pronouns &amp; Apostrophes
</h2>
<p className="text-lg text-slate-500 mb-8">
Pronoun errors are among the most commonly tested items in Form,
Structure &amp; Sense.
</p>
{/* Relative Pronouns */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-purple-50 border border-purple-200 space-y-4">
<h3 className="text-lg font-bold text-purple-900">Pronoun Rules</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-purple-600 text-white">
<th className="px-3 py-2 text-left text-xs">Pronoun</th>
<th className="px-3 py-2 text-left text-xs">Used For</th>
<th className="px-3 py-2 text-left text-xs">Example</th>
</tr>
</thead>
<tbody>
{[
[
"who / whom",
"People only",
'"the researcher who discovered…" / "the scientist whom they selected…"',
],
[
"which",
"Things / non-people (non-essential)",
'"the study, which examined 500 people…" — always gets a comma',
],
[
"that",
"Things / non-people (essential)",
'"the study that confirmed the theory" — no comma',
],
[
"where",
"Places",
'"the laboratory where the experiment occurred…"',
],
["when", "Times", '"the decade when the research began…"'],
].map(([pron, use, ex], i) => (
<tr
key={pron}
className={i % 2 === 0 ? "bg-white" : "bg-purple-50"}
>
<td className="px-3 py-2 font-bold text-purple-800 text-xs whitespace-nowrap">
{pron}
</td>
<td className="px-3 py-2 text-slate-600 text-xs">
{use}
</td>
<td className="px-3 py-2 text-slate-600 text-xs italic">
{ex}
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="bg-purple-900 text-white rounded-xl p-4">
<p className="font-bold text-sm mb-1">
Who vs. Whom Substitution Test
</p>
<ul className="text-xs text-purple-100 space-y-1">
<li>
<span className="font-bold">Who</span> = subject (substitutes
he/she): "Who wrote the report?" "He wrote it."
</li>
<li>
<span className="font-bold">Whom</span> = object (substitutes
him/her): "To whom did you give it?" "You gave it to him."
</li>
</ul>
</div>
<p className="text-sm font-semibold text-slate-800">
Additional Pronoun Rules tap to reveal:
</p>
<RevealCardGrid
cards={PRONOUN_RULES}
columns={3}
accentColor="purple"
/>
</div>
{/* Apostrophes */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">Apostrophes</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-purple-50 border border-purple-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Noun Apostrophes (Possession)
</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>
<span className="font-bold">Singular noun:</span> add 's →
the scientist's data
</li>
<li>
<span className="font-bold">Plural noun ending in s:</span>{" "}
add ' → the scientists' lab
</li>
<li>
<span className="font-bold">
Plural noun NOT ending in s:
</span>{" "}
add 's → the children's results
</li>
</ul>
</div>
<div className="card-tilt bg-purple-50 border border-purple-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Pronoun Apostrophes (Contractions Only)
</p>
<div className="grid grid-cols-2 gap-x-2 gap-y-1 text-xs">
{[
["its", "possessive"],
["it's", "it is / it has"],
["their", "possessive"],
["they're", "they are"],
["whose", "possessive"],
["who's", "who is / who has"],
["your", "possessive"],
["you're", "you are"],
].map(([form, meaning]) => (
<div key={form} className="flex gap-1">
<span className="font-bold text-purple-700">{form}</span>
<span className="text-slate-500">= {meaning}</span>
</div>
))}
</div>
</div>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-1">
Apostrophe Shortcut
</p>
<p className="text-xs text-slate-700">
If you can read the word as two words (it is it's), it's a
contraction use the apostrophe. Pronouns{" "}
<span className="font-bold">never</span> use apostrophes for
possession (opposite of nouns).
</p>
</div>
</div>
</section>
{/* ── Section 3: Modifiers, Parallel Structure & Commas ── */}
<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-2">
Modifiers, Parallel Structure &amp; Commas
</h2>
<p className="text-lg text-slate-500 mb-8">
Three interrelated skills that test whether elements are correctly
placed and balanced.
</p>
{/* §8.5 Modification */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-purple-50 border border-purple-200 space-y-4">
<h3 className="text-lg font-bold text-purple-900">
Modification Errors
</h3>
<div className="space-y-3">
<div className="card-tilt bg-white border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-2">
Dangling Modifier
</p>
<p className="text-xs text-slate-500 mb-2">
The modifier has no logical subject in the sentence, or the
subject it should modify does not immediately follow it.
</p>
<ul className="space-y-1 text-xs">
<li className="text-red-600">
"Running quickly, the bus was missed." The bus isn't
running.
</li>
<li className="text-green-700">
✓ "Running quickly, she missed the bus." — She was running;
subject appears right after comma.
</li>
<li className="text-amber-700 italic mt-1">
Possessive Trap: "Wishing to pass,{" "}
<strong>the student's</strong> exam" — the possessive makes
"exam" the subject. Use: "Wishing to pass,{" "}
<strong>the student</strong> reviewed"
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-2">
Misplaced Modifier
</p>
<p className="text-xs text-slate-500 mb-2">
The modifier is positioned too far from the noun it modifies.
</p>
<ul className="space-y-1 text-xs">
<li className="text-red-600">
✗ "She only eats salad." — only implies she does nothing
else?
</li>
<li className="text-green-700">
✓ "She eats only salad." — only modifies "salad."
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-purple-800 text-sm mb-2">
Parenthetical Modifiers — Matching Punctuation
</p>
<p className="text-xs text-slate-500 mb-2">
A non-essential phrase must be enclosed in matching
punctuation: two commas, two dashes, or two parentheses. Never
mix them.
</p>
<ul className="space-y-1 text-xs">
<li className="text-red-600">
✗ "The researcher, who had published 40 papers presented
her findings."
</li>
<li className="text-green-700">
✓ "The researcher, who had published 40 papers, presented
her findings."
</li>
<li className="text-green-700">
✓ "The researcher who had published 40 papers presented
her findings."
</li>
</ul>
</div>
</div>
</div>
{/* §8.6 Parallel Structure */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Parallel Structure
</h3>
<div className="space-y-2">
{[
{
type: "Lists",
bad: "She enjoys hiking, to swim, and reading.",
good: "She enjoys hiking, swimming, and reading.",
note: "All items must be the same grammatical form.",
},
{
type: "Correlative Pairs (not onlybut also, bothand, eitheror)",
bad: "He not only speaks French but also is fluent in German.",
good: "He not only speaks French but also speaks German.",
note: 'What follows "not only" must be parallel to what follows "but also."',
},
{
type: "Comparisons",
bad: "Running is healthier than to sit all day.",
good: "Running is healthier than sitting all day.",
note: "Both sides of a comparison must be the same form.",
},
{
type: "Semicolon Lists",
bad: "The study examined diet; the exercise habits; and their stress levels.",
good: "The study examined diet; exercise habits; and stress levels.",
note: "Items separated by semicolons must be parallel.",
},
].map((p) => (
<div
key={p.type}
className="card-tilt bg-slate-50 border border-slate-200 rounded-xl p-4"
>
<p className="font-bold text-purple-800 text-xs mb-1">
{p.type}
</p>
<p className="text-xs text-red-600">✗ {p.bad}</p>
<p className="text-xs text-green-700">✓ {p.good}</p>
<p className="text-xs text-slate-400 italic mt-1">{p.note}</p>
</div>
))}
</div>
</div>
{/* §8.7 Commas */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-purple-50 border border-purple-200 space-y-4">
<h3 className="text-lg font-bold text-purple-900">Comma Rules</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-green-200 rounded-xl p-4">
<p className="font-bold text-green-800 text-sm mb-2">
When a Comma IS Required
</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>
✓ After an introductory element at the start of a sentence.
</li>
<li>
✓ Before and after a non-essential (parenthetical) clause.
</li>
<li>✓ Before a FANBOYS joining two independent clauses.</li>
<li>✓ Between items in a list of three or more.</li>
<li>
✓ Between coordinate adjectives that independently modify
the noun.
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-2">
When a Comma Is NOT Used
</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>✗ Between a subject and its verb.</li>
<li>
✗ Before a subordinate clause that follows the main clause
("left because she was tired" — no comma before "because").
</li>
<li>
✗ Before a FANBOYS when one side is a phrase, not an IC.
</li>
<li>✗ After a FANBOYS conjunction.</li>
<li>✗ Between a verb and its object or complement.</li>
</ul>
</div>
</div>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-purple-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-purple-100">
For SVA: always cross out the material between subject and verb.
For modifiers: the noun being modified must immediately follow the
modifying phrase. For apostrophes: pronouns never use apostrophes
for possession — only for contractions.
</p>
</div>
</section>
{/* ── Section 4: SVA Decision Tree ── */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
SVA Decision Tree
</h2>
<p className="text-lg text-slate-500 mb-8">
Work through subject-verb agreement step by step. Find the true
subject, then match the verb.
</p>
<DecisionTreeWidget scenarios={TREE_SCENARIOS} accentColor="purple" />
</section>
{/* ── Section 5: Practice ── */}
<section
ref={(el) => {
sectionsRef.current[5] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Questions
</h2>
{FORM_STRUCTURE_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="purple" />
))}
{FORM_STRUCTURE_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="purple" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-purple-900 text-white font-bold rounded-full hover:bg-purple-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWFormStructureSenseLesson;

View File

@ -287,7 +287,7 @@ const EBRWGraphicDisplaysLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Reading Graphics" icon={BookOpen} /> <SectionMarker index={0} title="Reading Graphics" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -1,60 +1,107 @@
import React, { useRef, useState, useEffect } from "react"; import React, { useRef, useState, useEffect } from "react";
import { ArrowDown, Check, BookOpen, AlertTriangle, Zap } from "lucide-react"; import { Check, BookOpen, Lightbulb, Zap, Target } from "lucide-react";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell"; import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import { import {
INFERENCES_EASY, INFERENCES_EASY,
INFERENCES_MEDIUM, INFERENCES_MEDIUM,
} from "../../../data/rw/inferences"; } from "../../../data/rw/inferences";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps { interface LessonProps {
onFinish?: () => void; onFinish?: () => void;
} }
/* ── Data for RevealCardGrid widgets ── */
const INFERENCE_TYPES: RevealCard[] = [
{
label: "Valid / Necessary",
sublabel: "CORRECT on SAT",
content:
"Must be true if the stated evidence is true. Example: If 14% of earthquakes are supershear → 86% are NOT supershear.",
},
{
label: "Possible / Speculative",
sublabel: "WRONG on SAT",
content:
'Could be true, but might not be; goes further than the evidence. Example: "Researchers must want more funding" — not stated.',
},
{
label: "Contradicted",
sublabel: "WRONG on SAT",
content:
'Directly conflicts with stated evidence. Example: "Exercise improves fitness equally for all" when passage says otherwise.',
},
{
label: "Real-world true but unsupported",
sublabel: "WRONG on SAT",
content:
"True in reality but not implied by the passage. The SAT only rewards what the text guarantees.",
},
{
label: "Half-valid",
sublabel: "WRONG on SAT",
content:
"First part follows from the evidence, but the second part goes beyond what the evidence requires.",
},
];
const CHAIN_ERRORS: RevealCard[] = [
{
label: "Stopping too early",
content:
'Stopping at the finding rather than the conclusion (e.g., stopping at "feldspar was found" rather than following its implication for the competing theories).',
},
{
label: "Skipping a step",
content:
"Jumping to a conclusion that goes further than the chain allows by skipping an intermediate logical step.",
},
{
label: "Scope confusion",
content:
"The chain applies to one specific region or group, but the answer makes a universal claim about all regions or groups.",
},
{
label: "Time assumption",
content:
"The evidence describes a current or past state, and the answer makes a claim about a future state without justification.",
},
];
const EVIDENCE_EXERCISES: EvidenceExercise[] = [ const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{ {
question: question:
"What can be inferred from this passage about the long-term effects of the policy?", "Based on the passage, which sentence provides the strongest basis for inferring that Dr. Patel's research contradicts established scientific consensus?",
passage: [ passage: [
"When the city introduced congestion pricing in 2019, many business owners predicted economic disaster.", "Dr. Patel has spent fifteen years studying migration patterns of monarch butterflies.",
"Three years later, traffic in the city center had declined by 28%, and air quality had measurably improved.", "Her field stations span the entire North American flyway, from Canada to Mexico.",
"Revenue from the pricing scheme was reinvested in public transit, increasing bus and metro frequency by 40%.", "She has published 47 peer-reviewed papers, each building on data collected across multiple seasons.",
"Business revenues in the city center rose by an average of 12% over the same period, contradicting earlier fears.", "Her most recent paper challenges the assumption that butterflies navigate primarily by magnetic fields.",
"Several other major cities are now closely studying the program as a potential model.", "Instead, she proposes that polarized light plays a more significant role than previously recognized.",
], ],
evidenceIndex: 3, evidenceIndex: 3,
explanation: 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.", 'Sentence 4 is the basis for inferring contradiction with consensus. The word "challenges" indicates Dr. Patel is actively contradicting an established "assumption." This makes the inference valid — not speculation — because the passage directly states she is challenging the field.',
}, },
{ {
question: question:
"What does the passage imply about the relationship between diet and cognitive decline?", "Which sentence provides the strongest basis for inferring that the plastic bag ban had unintended consequences?",
passage: [ passage: [
"Alzheimer's disease affects more than 55 million people worldwide.", "In 2015, the city council banned plastic bags at all grocery stores.",
"In recent years, researchers have shifted focus from genetic factors alone to lifestyle factors, including diet.", "The ban was intended to reduce plastic waste in local waterways.",
"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.", "Plastic bag litter in rivers decreased by 40% in the first year.",
"However, researchers caution that correlation does not establish causation, and no single food has been proven to prevent Alzheimer's.", "However, sales of heavier-gauge trash bags increased by 350% over the same period.",
"Still, the evidence is strong enough that many neurologists now discuss dietary patterns with patients at risk.", "Environmental analysts noted that thick trash bags contain more plastic by weight than the thin bags they replaced.",
], ],
evidenceIndex: 2, evidenceIndex: 3,
explanation: 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.', "Sentence 4 is the evidence of the unintended consequence — a massive surge in heavier plastic bag purchases. Combined with sentence 5, it implies total plastic use may have increased, the opposite of the ban's stated goal.",
},
{
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.",
}, },
]; ];
@ -78,9 +125,11 @@ const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
return () => observers.forEach((o) => o.disconnect()); return () => observers.forEach((o) => o.disconnect());
}, []); }, []);
const scrollToSection = (i: number) => { useScrollReveal();
setActiveSection(i);
sectionsRef.current[i]?.scrollIntoView({ behavior: "smooth" }); const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.scrollIntoView({ behavior: "smooth" });
}; };
const SectionMarker = ({ const SectionMarker = ({
@ -100,7 +149,8 @@ const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
className={`flex items-center gap-3 p-3 w-full rounded-lg text-left transition-all ${isActive ? "bg-teal-50" : "hover:bg-slate-50"}`} 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 <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"}`} 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 ? ( {isPast ? (
<Check className="w-4 h-4" /> <Check className="w-4 h-4" />
@ -119,192 +169,463 @@ const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker index={0} title="Valid Inferences" icon={BookOpen} />
index={0}
title="Concept & Annotation"
icon={BookOpen}
/>
<SectionMarker <SectionMarker
index={1} index={1}
title="Evidence Hunter" title="Inference Patterns"
icon={AlertTriangle} icon={Lightbulb}
/> />
<SectionMarker index={2} title="Practice Quiz" icon={Zap} />
<SectionMarker index={2} title="Inference Tracker" icon={Target} />
<SectionMarker index={3} title="Practice Questions" icon={Zap} />
</nav> </nav>
</aside> </aside>
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto"> <div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* Section 0: Concept & Annotation */} {/* Section 0 — Valid Inferences */}
<section <section
ref={(el) => { ref={(el) => {
sectionsRef.current[0] = el; sectionsRef.current[0] = el;
}} }}
className="min-h-screen flex flex-col justify-center mb-24 pt-20 lg:pt-0" 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"> <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 w-fit">
Information & Ideas Information &amp; Ideas Domain 2
</div> </div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2"> <h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Inferences Inferences
</h2> </h2>
<p className="text-lg text-slate-500 mb-8"> <p className="text-lg text-slate-500 mb-8">
A valid inference is not stated but is strongly supported. It never A valid inference MUST be true based on the passage not merely
exceeds what the text supports and never uses extreme language. possible, plausible, or consistent.
</p> </p>
{/* Rule grid */} {/* Core Concept — now with RevealCardGrid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8"> <div className="scroll-reveal stagger-1 bg-teal-50 border border-teal-200 rounded-2xl p-6 mb-8">
{[ <h3 className="text-lg font-bold text-teal-900 mb-2">
{ The Core Concept What Makes an Inference Valid?
num: "1", </h3>
title: "Inference = Logical Extension", <p className="text-sm text-slate-700 mb-4">
body: "An inference is not stated directly. It's a conclusion that must logically follow from what the text says.", A valid inference is a statement that MUST be true if the evidence
}, is true. It cannot merely be likely, plausible, or consistent. The
{ SAT rewards only conclusions that necessarily follow from what is
num: "2", stated.
title: "Stay Close to the Text", </p>
body: "The SAT rewards inferences that are a small, necessary step from the evidence. Avoid dramatic leaps.", <RevealCardGrid
}, cards={INFERENCE_TYPES}
{ columns={3}
num: "3", accentColor="teal"
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.", </div>
},
{ {/* 3-Step Text Completion Process */}
num: "4", <div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
title: "Eliminate Extreme Language", <h3 className="text-lg font-bold text-slate-900">
body: "Inferences with 'always,' 'never,' 'all,' 'none,' 'impossible' are almost always wrong — the passage rarely proves absolutes.", The 3-Step Text Completion Process
}, </h3>
].map((rule) => ( <p className="text-sm text-slate-600">
<div Text completion questions present a short passage followed by a
key={rule.num} blank at the end, asking "Which choice most logically completes
className="rounded-2xl border border-teal-200 bg-teal-50 p-5" the text?" The correct answer NECESSARILY follows from the stated
> evidence not merely the most interesting or plausible
<div className="flex items-center gap-2 mb-2"> continuation.
<span className="w-7 h-7 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold shrink-0"> </p>
{rule.num} <div className="bg-teal-50 border border-teal-200 rounded-xl p-4">
</span> <p className="font-bold text-teal-800 text-sm mb-2">
<p className="text-sm font-bold text-teal-900"> Universal Method for All Inference Questions
{rule.title} </p>
</p> <div className="space-y-2">
</div> {[
<p className="text-sm text-slate-700 leading-relaxed"> [
{rule.body} "1",
</p> "IDENTIFY the key claim or evidence (usually in the 12 sentences before the blank). Write it in 36 words on scratch paper.",
],
[
"2",
'WORK OUT the implication: "If this is true, what MUST also be true?" Do this BEFORE looking at answers. Even one word helps.',
],
[
"3",
"MATCH the answer: look for the option that says the same thing as your implication, possibly using different words, negation, or synonyms.",
],
].map(([n, text]) => (
<div key={n} className="flex gap-2">
<span className="w-5 h-5 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{n}
</span>
<p className="text-xs text-slate-700">{text}</p>
</div>
))}
</div> </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>
<div className="rounded-xl bg-green-100 border border-green-200 p-4"> <div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="text-xs font-bold text-green-700 uppercase tracking-wider mb-1"> <p className="font-bold text-amber-800 text-sm mb-1">
Valid inference WHY THIS MATTERS
</p> </p>
<p className="text-sm text-slate-800"> <p className="text-xs text-slate-700">
Sleep deprivation negatively affects memory performance. If you look at answer choices before working out the
</p> implication, you are vulnerable to speculation traps answers
</div> that sound plausible because they extend the idea in a
<div className="rounded-xl bg-orange-100 border border-orange-200 p-4"> reasonable direction, but go further than the evidence requires.
<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> </p>
</div> </div>
</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>
{/* Section 1: Evidence Hunter */} {/* Section 1 — Inference Patterns */}
<section <section
ref={(el) => { ref={(el) => {
sectionsRef.current[1] = el; sectionsRef.current[1] = el;
}} }}
className="min-h-screen flex flex-col justify-center mb-24" 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"> <h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Evidence Hunter Inference Patterns
</h2> </h2>
<p className="text-lg text-slate-500 mb-8"> <p className="text-lg text-slate-500 mb-8">
For each passage, click the sentence that most strongly supports the The four core patterns, speculation traps, double negatives, and
inference asked. Think: which sentence does the most work for this multi-step chains.
conclusion?
</p> </p>
<EvidenceHunterWidget {/* 6A — 4 Core Patterns */}
exercises={EVIDENCE_EXERCISES} <div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
accentColor="teal" <h3 className="text-lg font-bold text-teal-900">
/> The Four Core Valid Inference Patterns
</h3>
<div className="space-y-4">
{[
{
num: 1,
pattern: "Negation / Contrapositive",
rule: "If only X has property Y, then everything that is not-X must lack property Y. This works in both directions.",
examples: [
"If 14% of earthquakes are supershear events → 86% are NOT supershear events.",
"If a phenomenon occurs ONLY during slow-wave sleep → it does NOT occur during REM sleep.",
"If researchers focused mostly on land → most data comes from land, therefore they may have undercounted non-land events.",
],
},
{
num: 2,
pattern: "Relative Comparison",
rule: "If X is more than Y, you can restate this as Y is less than X. If X is the most, you can infer that all others are less than X.",
examples: [
"If muscular contractions when lowering weights are MOST effective → contractions when raising weights are LESS effective.",
"If big brown bats emit the most cries → all other species emit fewer cries.",
"Restatement direction: if the claim is A > B, a valid answer might say B < A.",
],
},
{
num: 3,
pattern: "Logical Elimination",
rule: "When possibilities are listed and most are ruled out, the remaining possibility is the valid inference.",
examples: [
"Researchers studied supershear earthquakes mostly on land → they did not study underwater earthquakes → many supershear earthquakes likely occur underwater.",
"Two theories for Mars's crust: (a) magma ocean or (b) different origin. If feldspar (associated with b) is found → theory (a) alone does not explain the crust.",
],
},
{
num: 4,
pattern: "Causal / Consequential Extension",
rule: "If X causes Y, then applying X to a new situation should produce Y. Removing X should reduce Y.",
examples: [
"If replay during slow-wave sleep consolidates memory → dancers who sleep several hours right after learning a routine should remember it better two weeks later.",
"If hyperglycemia (high blood sugar) reduces exercise response → a drug that lowers blood sugar should improve exercise response.",
"Apply the mechanism to a new group, new time period, or new scenario — but STAY within the same causal framework.",
],
},
].map((p) => (
<div
key={p.num}
className="card-tilt bg-white border border-slate-200 rounded-xl p-5"
>
<div className="flex items-center gap-2 mb-2">
<span className="w-6 h-6 rounded-full bg-teal-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{p.num}
</span>
<p className="font-bold text-teal-900">{p.pattern}</p>
</div>
<p className="text-sm text-slate-700 mb-2">{p.rule}</p>
<ul className="space-y-1">
{p.examples.map((ex, i) => (
<li
key={i}
className="text-xs text-slate-500 bg-slate-50 rounded px-2 py-1 italic"
>
{ex}
</li>
))}
</ul>
</div>
))}
</div>
</div>
<button {/* 6B — Speculation Traps */}
onClick={() => scrollToSection(2)} <div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
className="mt-12 group flex items-center text-teal-600 font-bold hover:text-teal-800 transition-colors" <h3 className="text-lg font-bold text-slate-900">
> Speculation Traps
Next: Practice Quiz{" "} </h3>
<ArrowDown className="ml-2 w-5 h-5 group-hover:translate-y-1 transition-transform" /> <p className="text-sm text-slate-600">
</button> Speculation traps are the most common wrong answer type in text
completions. They go one plausible step too far beyond what the
evidence necessarily implies.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-red-50 border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-2">
How to Identify a Speculation Trap
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li> The answer could be true but doesn't have to be.</li>
<li>
• The answer introduces a new assumption not grounded in the
passage.
</li>
<li>
• The answer requires you to imagine a scenario the passage
doesn't describe.
</li>
<li>
The answer goes from "this happens" to "this is the
best/only/most effective way."
</li>
</ul>
</div>
<div className="card-tilt bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
Worked Example
</p>
<p className="text-xs text-slate-600 mb-2 italic">
Evidence: Sea turtle conservation focuses on protecting
hatchlings after they emerge.
</p>
<ul className="text-xs space-y-1">
<li className="text-green-700">
VALID: Conservation focuses less on hatchlings before they
emerge.
</li>
<li className="text-red-600">
TRAP: Protecting hatchlings after emergence is the only
effective method.
</li>
<li className="text-red-600">
TRAP: Pre-emergence protection is more effective.
</li>
</ul>
</div>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-800 text-sm mb-1">RULE</p>
<p className="text-xs text-slate-700">
The word "only" in an answer choice is almost always a sign of
over-speculation. Very few things on the SAT are literally "the
only way." Be very suspicious of absolute claims in answer
choices.
</p>
</div>
</div>
{/* 6C — Double Negatives & Second Meanings */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Double Negatives and Second Meanings in Answer Choices
</h3>
<p className="text-sm text-slate-600">
Inference answer choices frequently use double negatives (which
create positive meanings) or words in their second meanings. These
are designed to make correct answers look wrong to careless
readers.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
Double Negative Translations
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li> "Not impossible" = possible</li>
<li> "Not unimportant" = important</li>
<li> "Not unlike" = similar</li>
<li> "Less harmful" = milder, but still harmful</li>
<li> Decode completely before evaluating the answer.</li>
<li className="italic text-teal-700">
Strategy: Replace "not un-X" with "X" and "not im-X" with
"X".
</li>
</ul>
</div>
<div className="card-tilt bg-teal-50 border border-teal-200 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
Second Meanings to Know
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
"Qualify" = limit the scope of a claim (not: meet
requirements)
</li>
<li> "Sound" = valid, reliable (not: audio)</li>
<li> "Check" = restrain, control (not: verify)</li>
<li>
"Economy" = thrift, efficiency (not: the financial system)
</li>
<li> "Reserve" = hold off on (not: book in advance)</li>
<li className="italic text-teal-700">
When a simple common word appears as an answer, suspect a
second meaning.
</li>
</ul>
</div>
</div>
</div>
{/* 6D — Multi-Step Logical Chains */}
<div className="scroll-reveal stagger-4 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Multi-Step Logical Chains
</h3>
<p className="text-sm text-slate-600">
Some text completion questions require you to follow 23 logical
steps before arriving at the conclusion. Students most often lose
points here by stopping one step too early or introducing an extra
assumption.
</p>
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4">
<p className="font-bold text-slate-800 text-sm mb-2">
Multi-Step Chain: Worked Example (Mars Crust)
</p>
<div className="space-y-1 text-xs text-slate-700">
<p>
<span className="font-bold">Step 1:</span> Two theories exist
for the first Martian crust: (a) all-encompassing magma ocean,
(b) different origin with high silica.
</p>
<p>
<span className="font-bold">Step 2:</span> Researchers find
feldspar (associated with high-silica lava flows) in 9
locations on Mars.
</p>
<p>
<span className="font-bold">Step 3:</span> Feldspar is
evidence for Theory (b) different origin with high silica.
</p>
<p>
<span className="font-bold">Step 4:</span> If Theory (b)
accounts for some locations, Theory (a) (magma ocean) could
not have been ALL-ENCOMPASSING.
</p>
<p className="text-green-700 font-bold mt-2">
Valid Conclusion: The magma ocean was not all-encompassing.
</p>
<p className="text-red-600 italic">
Speculation Trap: "Portions of Mars' surface were never
covered by a crust." (no evidence for this)
</p>
</div>
</div>
<p className="font-semibold text-sm text-slate-800">
Common errors in multi-step chains:
</p>
<RevealCardGrid
cards={CHAIN_ERRORS}
columns={2}
accentColor="teal"
/>
</div>
{/* 6E — Scratch Paper */}
<div className="scroll-reveal stagger-5 rounded-2xl p-6 mb-8 bg-teal-50 border border-teal-200 space-y-4">
<h3 className="text-lg font-bold text-teal-900">
Using Scratch Paper for Inference Questions
</h3>
<p className="text-sm text-slate-700">
For text completions, scratch paper is not optional it is
essential. Writing down even a brief summary of the key claim and
your predicted answer protects you from being seduced by
plausible-sounding wrong answers.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-teal-100 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
What to Write
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li> 36 word summary of the key claim.</li>
<li>
Arrow indicating direction: "X → Y" or "less X = more Y".
</li>
<li>
Your predicted answer in 35 words before looking at
choices.
</li>
<li>
For multi-step chains: number each step (1) (2) (3).
</li>
</ul>
</div>
<div className="card-tilt bg-white border border-teal-100 rounded-xl p-4">
<p className="font-bold text-teal-800 text-sm mb-2">
What This Prevents
</p>
<ul className="text-xs text-slate-600 space-y-1">
<li>
Choosing a speculative answer because it sounded good.
</li>
<li>
Forgetting the specific constraint after reading 4 answer
choices.
</li>
<li> Losing track of the logical chain halfway through.</li>
<li>
Selecting half-valid answers that address only part of the
claim.
</li>
</ul>
</div>
</div>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-teal-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-teal-100">
Test every answer with: "Does the passage GUARANTEE this is true?"
If you can imagine a scenario where the passage is correct but
this answer is still false, eliminate it. Only the answer that
MUST be true is correct. "Only," "best," and "most effective" in
answer choices = almost always over-speculation.
</p>
</div>
</section> </section>
{/* Section 2: Practice Quiz */} {/* Section 2 — Inference Tracker widget */}
<section <section
ref={(el) => { ref={(el) => {
sectionsRef.current[2] = el; sectionsRef.current[2] = el;
}} }}
className="min-h-screen flex flex-col justify-center mb-24" className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Inference Tracker
</h2>
<p className="text-lg text-slate-500 mb-8">
Click the sentence that provides the strongest basis for the
inference. Which sentence GUARANTEES the conclusion?
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="teal"
/>
</section>
{/* Section 3 — Practice */}
<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"> <h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Quiz Practice Questions
</h2> </h2>
{INFERENCES_EASY.slice(0, 2).map((q) => ( {INFERENCES_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="teal" /> <PracticeFromDataset key={q.id} question={q} color="teal" />
@ -317,7 +638,7 @@ const EBRWInferencesLesson: React.FC<LessonProps> = ({ onFinish }) => {
onClick={onFinish} onClick={onFinish}
className="px-6 py-3 bg-teal-900 text-white font-bold rounded-full hover:bg-teal-700 transition-colors" className="px-6 py-3 bg-teal-900 text-white font-bold rounded-full hover:bg-teal-700 transition-colors"
> >
Finish Lesson Finish Lesson
</button> </button>
</div> </div>
</section> </section>

View File

@ -116,7 +116,7 @@ const EBRWMainIdeaLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker
index={0} index={0}

View File

@ -238,7 +238,7 @@ const EBRWPronounsLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Pronoun Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Pronoun Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -0,0 +1,633 @@
import React, { useRef, useState, useEffect } from "react";
import {
Check,
BookOpen,
Lightbulb,
Zap,
Target,
AlertTriangle,
} from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
RHETORICAL_EASY,
RHETORICAL_MEDIUM,
} from "../../../data/rw/rhetorical-synthesis";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import RevealCardGrid, {
type RevealCard,
} from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
/* ── Data for RevealCardGrid widgets ── */
const RHETORICAL_GOALS: RevealCard[] = [
{
label: "Introduce a Topic",
sublabel: '"introduce," "background," "context"',
content: "Present a new subject with context. No strong argument yet.",
},
{
label: "Make a Comparison",
sublabel: '"compare," "contrast," "differ," "between"',
content:
"Explicitly contrast OR compare two things. Must mention BOTH and their relationship.",
},
{
label: "Emphasize Similarity",
sublabel: '"similarity," "common," "alike," "both"',
content:
'Highlight what two things have IN COMMON. Both must appear; "both" or "similarly" usually required.',
},
{
label: "Illustrate with Example",
sublabel: '"example," "illustrate," "such as"',
content:
"Use a specific case to demonstrate a general principle. Must contain a concrete example.",
},
{
label: "State a Limitation",
sublabel: '"limitation," "drawback," "only," "cannot"',
content:
"Acknowledge a restriction, exception, or weakness of a claim or finding.",
},
{
label: "Present Cause-Effect",
sublabel: '"cause," "result," "lead to," "effect"',
content:
"Show a causal chain: X leads to Y, or X results in Y. The relationship must be directional.",
},
{
label: "Provide Supporting Evidence",
sublabel: '"support," "evidence," "data," "findings"',
content:
"Give data, statistics, or findings that back up a claim already made.",
},
{
label: "Acknowledge Opposing View",
sublabel: '"opposing," "counterargument," "critics"',
content:
'Present a counterargument before making your own point. "Critics argue…" structure.',
},
];
const WRONG_PATTERNS: RevealCard[] = [
{
label: "Wrong Goal",
sublabel: "Check this FIRST",
content:
"Achieves a different purpose from what the question asks. Most common wrong answer type — check before verifying facts.",
},
{
label: "Accurate But Irrelevant",
content:
"Uses real facts from the notes, but from the wrong notes — not the ones that serve the stated goal.",
},
{
label: "Distorted Fact",
content:
"Changes a number, reverses a direction, or misrepresents what a note says. Always verify facts word by word.",
},
{
label: "Incompatible Notes Combined",
content:
"Joins two notes that cannot logically work together, producing individually accurate but collectively incoherent sentences.",
},
{
label: "Wrong Scope",
content:
"Too broad (covers more than goal requires) or too narrow (picks a detail that misses the stated purpose).",
},
{
label: "Outside Information",
content:
"Claims something not found in any provided notes — even if generally true in the real world. If you can't point to the note, eliminate it.",
},
];
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question:
"A student wants to EMPHASIZE A SIMILARITY between two renewable energy sources. Which sentence best accomplishes this goal?",
passage: [
"Wind energy capacity in the US grew by 14% in 2022.",
"Solar energy capacity in the US also grew by 14% in 2022.",
"Wind turbines require more land per megawatt than solar panels.",
"Both wind and solar energy produce no direct carbon emissions during operation.",
"Government subsidies for renewable energy totaled $15 billion in 2022.",
],
evidenceIndex: 3,
explanation:
'Sentence 4 directly emphasizes a shared characteristic of both sources. The word "both" signals similarity, and "no direct carbon emissions" is the specific trait they share — this is exactly what the goal requires.',
},
{
question:
"A student wants to ACKNOWLEDGE AN OPPOSING VIEW about urban farming before presenting their own argument. Which sentence best accomplishes this goal?",
passage: [
"Urban farming initiatives have expanded in over 200 American cities.",
"Critics argue that urban farms cannot produce food at a scale sufficient to affect food insecurity.",
"Urban farms do provide fresh produce in food deserts, regardless of total volume.",
"Community gardens have been shown to strengthen neighborhood social ties.",
"The average urban farm yields 5 times more produce per square foot than conventional farms.",
],
evidenceIndex: 1,
explanation:
'Sentence 2 explicitly presents a counterargument with "Critics argue..." — the standard structure for acknowledging an opposing view. It sets up the concession the student needs before making their own point.',
},
];
const EBRWRhetoricalSynthesisLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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-rose-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
${isActive ? "bg-rose-600 text-white" : isPast ? "bg-rose-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-rose-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="The Core Concept" icon={BookOpen} />
<SectionMarker
index={1}
title="Goals &amp; Strategy"
icon={AlertTriangle}
/>
<SectionMarker index={2} title="Worked Example" icon={Target} />
<SectionMarker index={3} title="Goal Matcher" icon={Lightbulb} />
<SectionMarker index={4} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* ── Section 0: The Core Concept ── */}
<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-rose-100 text-rose-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4 w-fit">
Expression of Ideas Domain 4
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Rhetorical Synthesis
</h2>
<p className="text-lg text-slate-500 mb-8">
Every answer is judged on two things: does it achieve the stated
goal, and does every fact come from the notes? Failing either test
means wrong no exceptions.
</p>
{/* §9.1 — What Makes an Answer Correct */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-rose-50 border border-rose-200 space-y-4">
<h3 className="text-lg font-bold text-rose-900">
The Two-Criteria Rule
</h3>
<p className="text-sm text-slate-700">
Unlike most SAT questions, Rhetorical Synthesis does not ask you
to find the "best" answer in a general sense. It asks you to find
the answer that satisfies exactly two requirements simultaneously.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="card-tilt bg-white border border-rose-200 rounded-xl p-4">
<p className="font-bold text-rose-900 text-sm mb-2">
Criterion 1 GOAL
</p>
<p className="text-xs text-slate-700">
The answer must accomplish exactly the rhetorical purpose
stated in the question no more, no less. An answer that does
something different from the stated goal is automatically
wrong, even if every fact is accurate.
</p>
</div>
<div className="card-tilt bg-white border border-rose-200 rounded-xl p-4">
<p className="font-bold text-rose-900 text-sm mb-2">
Criterion 2 ACCURACY
</p>
<p className="text-xs text-slate-700">
Every fact in the answer must come directly from the provided
notes nothing added, nothing distorted. Even one wrong
number or reversed relationship eliminates an answer, even if
the goal is otherwise met.
</p>
</div>
</div>
<div className="bg-rose-100 border border-rose-300 rounded-xl p-4">
<p className="text-xs text-slate-700">
<span className="font-bold text-rose-900">
The key insight:
</span>{" "}
These two criteria work independently. You can fail on goal
while passing accuracy. You can fail on accuracy while passing
goal. Only the answer that passes both is correct.
</p>
</div>
</div>
{/* §9.2 — Question Anatomy */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Question Anatomy
</h3>
<p className="text-sm text-slate-600">
Every Rhetorical Synthesis question has the same four-part
structure. Each part plays a specific role in determining the
correct answer.
</p>
<div className="overflow-x-auto">
<table className="w-full text-sm border-collapse rounded-xl overflow-hidden">
<thead>
<tr className="bg-rose-600 text-white">
<th className="px-3 py-2 text-left text-xs">Component</th>
<th className="px-3 py-2 text-left text-xs">
What It Contains
</th>
<th className="px-3 py-2 text-left text-xs">
What You Need From It
</th>
</tr>
</thead>
<tbody>
{[
[
"Context Header",
'"While researching a topic, a student took the following notes:"',
"Sets the topic domain. Establishes what field the notes come from.",
],
[
"Notes (24 bullets)",
"Factual information: data, definitions, examples, findings",
"The ONLY source of accurate facts. Every claim in the correct answer must trace back here.",
],
[
"Stated Goal",
'"The student wants to [X]. Which choice best accomplishes this goal?"',
"Read this FIRST — before notes, before answer choices. It tells you exactly what the correct answer must do.",
],
[
"Four Answer Choices",
"Sentences combining facts from the notes",
"Wrong answers fail the goal, distort facts, add outside information, or combine incompatible notes.",
],
].map(([comp, contains, need], i) => (
<tr
key={comp}
className={i % 2 === 0 ? "bg-white" : "bg-rose-50"}
>
<td className="px-3 py-2 font-bold text-rose-800 text-xs whitespace-nowrap">
{comp}
</td>
<td className="px-3 py-2 text-slate-600 text-xs italic">
{contains}
</td>
<td className="px-3 py-2 text-slate-700 text-xs">
{need}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</section>
{/* ── Section 1: Goals & Strategy ── */}
<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-2">
Goals &amp; Strategy
</h2>
<p className="text-lg text-slate-500 mb-8">
The eight rhetorical goals, the 5-step method, and the six ways
wrong answers fail.
</p>
{/* §9.3 — The 8 Goals */}
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-rose-50 border border-rose-200 space-y-4">
<h3 className="text-lg font-bold text-rose-900">
The 8 Rhetorical Goals tap to reveal what each answer must do:
</h3>
<p className="text-sm text-slate-700">
The stated goal will always be one of these eight types. Knowing
them lets you immediately identify what structural features the
correct answer must contain.
</p>
<RevealCardGrid
cards={RHETORICAL_GOALS}
columns={4}
accentColor="rose"
/>
</div>
{/* §9.5 — The 5-Step Strategy */}
<div className="scroll-reveal stagger-2 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
The 5-Step Strategy
</h3>
<div className="space-y-2">
{[
{
step: 1,
action: "Read the GOAL first",
tip: 'Before reading notes or answer choices, read the stated goal. Write the goal type in 34 words on scratch paper: e.g., "comparison — key difference." The goal determines everything else.',
},
{
step: 2,
action: "Read the notes with the goal in mind",
tip: "Identify which 12 notes are relevant to your goal. Cross out off-topic notes — they exist only as material for wrong answers.",
},
{
step: 3,
action: "Predict what the correct answer must contain",
tip: 'Before looking at choices, write a quick sketch: "must mention both species + a difference." Even a rough prediction blocks you from being swayed by sophisticated wrong answers.',
},
{
step: 4,
action: "Eliminate wrong goals first",
tip: "Any answer that achieves a DIFFERENT goal from the one stated is wrong — eliminate it before checking any facts. This alone usually removes 23 choices immediately.",
},
{
step: 5,
action: "Verify accuracy in the remaining answers",
tip: "Read each remaining answer word by word against the notes. One wrong number, reversed relationship, or unsupported claim eliminates the answer.",
},
].map((s) => (
<div
key={s.step}
className="flex gap-3 bg-rose-50 border border-rose-100 rounded-xl px-4 py-3"
>
<span className="w-6 h-6 rounded-full bg-rose-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{s.step}
</span>
<div>
<p className="font-bold text-slate-800 text-sm">
{s.action}
</p>
<p className="text-xs text-slate-500 mt-0.5">{s.tip}</p>
</div>
</div>
))}
</div>
</div>
{/* §9.4 — Wrong Answer Patterns */}
<div className="scroll-reveal stagger-3 rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Wrong Answer Patterns tap to reveal each trap:
</h3>
<p className="text-sm text-slate-600">
Every wrong answer fails in one of these six predictable ways.
Naming the failure pattern immediately tells you why to eliminate
it.
</p>
<RevealCardGrid
cards={WRONG_PATTERNS}
columns={3}
accentColor="rose"
/>
</div>
<div className="scroll-reveal-scale golden-rule-glow bg-rose-900 text-white rounded-2xl p-5 mb-8">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-rose-100">
Read the GOAL before reading anything else. An accurate answer
that achieves the wrong goal is wrong. An answer that achieves the
right goal with one distorted fact is also wrong. Both criteria
must be met.
</p>
</div>
</section>
{/* ── Section 2: Worked Example ── */}
<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-2">
Worked Example
</h2>
<p className="text-lg text-slate-500 mb-8">
A complete question with full analysis of why each answer succeeds
or fails against the two criteria.
</p>
<div className="scroll-reveal stagger-1 rounded-2xl p-6 mb-8 bg-slate-50 border border-slate-200 space-y-4">
<p className="text-xs font-bold text-slate-500 uppercase tracking-wider">
While researching a topic, a student took the following notes:
</p>
<ul className="space-y-2">
{[
"Komodo dragons (Varanus komodoensis) are the world's largest living lizard species, reaching up to 3 meters in length.",
"Komodo dragons are found only on five islands in Indonesia: Komodo, Rinca, Flores, Gili Motang, and Padar.",
"Komodo dragons have a venomous bite that prevents blood clotting in prey, causing prey to weaken from blood loss.",
"The IUCN lists Komodo dragons as Endangered, with an estimated 1,3832,531 individuals remaining in the wild.",
"Nile monitor lizards, native to Africa, are also large lizards but are not venomous and grow to only 2 meters.",
].map((note, i) => (
<li key={i} className="flex gap-2 text-sm text-slate-700">
<span className="text-rose-500 font-bold shrink-0">
{i + 1}.
</span>
{note}
</li>
))}
</ul>
<div className="bg-rose-100 border border-rose-300 rounded-xl p-3">
<p className="text-sm font-bold text-rose-900">
The student wants to COMPARE the Komodo dragon with another
large lizard species by highlighting a key difference. Which
choice best accomplishes this goal?
</p>
</div>
<div className="space-y-3">
{[
{
letter: "A",
correct: false,
text: "Komodo dragons, the world's largest living lizards at 3 meters, are endangered, with fewer than 2,531 individuals remaining.",
analysis:
"✗ Wrong goal. Only one species is mentioned — no comparison is made. The goal explicitly requires comparing Komodo dragons WITH another species.",
},
{
letter: "B",
correct: true,
text: "Unlike Nile monitor lizards, which are not venomous and reach only 2 meters, Komodo dragons possess a venomous bite and can grow up to 3 meters.",
analysis:
"✓ Correct. Both species appear. A key difference is highlighted (venomous vs. not venomous, 3m vs. 2m). All facts trace directly to Notes 1 and 5. Goal ✓, Accuracy ✓.",
},
{
letter: "C",
correct: false,
text: "Komodo dragons are venomous lizards found on five Indonesian islands, and their numbers have declined to fewer than 2,531 in the wild.",
analysis:
"✗ Wrong goal. Only one species mentioned — no comparison. The facts are accurate, but the goal is not achieved.",
},
{
letter: "D",
correct: false,
text: "Both Komodo dragons and Nile monitor lizards are large reptiles, with Komodo dragons endangered and Nile monitors thriving across Africa.",
analysis:
'✗ Two failures. First, the goal asks for a KEY DIFFERENCE, not similarity — "both are large reptiles" emphasizes similarity. Second, "Nile monitors thriving" appears in no note — outside information.',
},
].map((opt) => (
<div
key={opt.letter}
className={`rounded-xl p-4 border ${opt.correct ? "bg-green-50 border-green-300" : "bg-red-50 border-red-200"}`}
>
<div className="flex gap-3 items-start">
<span
className={`w-7 h-7 rounded-full flex items-center justify-center text-sm font-bold shrink-0 ${opt.correct ? "bg-green-600 text-white" : "bg-red-500 text-white"}`}
>
{opt.letter}
</span>
<div>
<p className="text-sm text-slate-800 italic mb-2">
"{opt.text}"
</p>
<p className="text-xs text-slate-600">{opt.analysis}</p>
</div>
</div>
</div>
))}
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-900 text-sm mb-1">Takeaways</p>
<ul className="space-y-1 text-xs text-slate-700">
<li>
"Compare by highlighting a key difference" = two species
must appear AND a difference must be stated.
</li>
<li>
A and C both used accurate facts but both failed the goal
test. Eliminated without checking accuracy.
</li>
<li>
D failed on both criteria: wrong goal type (similarity
instead of difference) AND outside information.
</li>
<li>
B is the only answer that passes both criteria: goal (two
species, key difference stated), accuracy (all facts from
Notes 1 and 5).
</li>
</ul>
</div>
</div>
</section>
{/* ── Section 3: Goal Matcher Widget ── */}
<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-2">
Goal Matcher
</h2>
<p className="text-lg text-slate-500 mb-8">
Find the sentence that best achieves the stated rhetorical goal
using only facts from the notes.
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="rose"
/>
</section>
{/* ── Section 4: Practice ── */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Questions
</h2>
{RHETORICAL_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="rose" />
))}
{RHETORICAL_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="rose" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-rose-900 text-white font-bold rounded-full hover:bg-rose-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWRhetoricalSynthesisLesson;

View File

@ -219,7 +219,7 @@ const EBRWSemicolonsColonsLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Clause Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Clause Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -244,7 +244,7 @@ const EBRWSentenceStructureLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Structure Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Structure Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -233,7 +233,7 @@ const EBRWSubjectVerbLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Agreement Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Agreement Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -0,0 +1,941 @@
import React, { useRef, useState, useEffect } from "react";
import { Check, BookOpen, Lightbulb, Zap, Star, BarChart3 } from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
TEXT_STRUCTURE_EASY,
TEXT_STRUCTURE_MEDIUM,
} from "../../../data/rw/text-structure-purpose";
import EvidenceHunterWidget, {
type EvidenceExercise,
} from "../../../components/lessons/EvidenceHunterWidget";
import DataClaimWidget, {
type DataExercise,
} from "../../../components/lessons/DataClaimWidget";
import RevealCardGrid from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
const EVIDENCE_EXERCISES: EvidenceExercise[] = [
{
question: "What is the main point of this passage?",
passage: [
"Scientists have long assumed that deep-sea creatures live in perpetual darkness.",
"However, a 2023 study by marine biologist Dr. Yuen found bioluminescent signals in fish previously believed to be blind.",
"The fish not only detect light but appear to use it for communication.",
"These findings suggest that vision may be far more widespread in the deep ocean than previously thought.",
"Future expeditions will need to reconsider how they classify deep-sea species.",
],
evidenceIndex: 3,
explanation:
'Sentence 4 states the "So What" — the surprising new conclusion about how widespread vision is. It is the main point, not a supporting detail. The word "suggest" introduces the author\'s key takeaway.',
},
{
question:
"Which sentence best identifies the author's PRIMARY PURPOSE in writing this passage?",
passage: [
"The Harlem Renaissance of the 1920s produced some of the most innovative jazz in American history.",
"Composers like Duke Ellington and Louis Armstrong transformed the harmonic language of popular music.",
"Yet historians have traditionally focused on the literary figures of this era while neglecting the musicians.",
"This essay argues that jazz musicians were the true cultural architects of the Harlem Renaissance.",
"By examining their influence on poetry and visual art, we can restore them to their rightful place in history.",
],
evidenceIndex: 3,
explanation:
'Sentence 4 contains the thesis — "This essay argues" is a classic purpose-signaling phrase that tells you both the PURPOSE (to argue) and the CLAIM (jazz musicians were the true architects). The other sentences provide context or support.',
},
];
const GRAPH_EXERCISES: DataExercise[] = [
{
title: "Bar — Book Format Preferences",
chart: {
type: "bar",
title: "Preferred Reading Format by Age Group (% of respondents)",
yLabel: "% of respondents",
xLabel: "Age Group",
unit: "%",
source: "National Reading Survey, 2023",
series: [
{
name: "Print Books",
data: [
{ label: "1824", value: 32 },
{ label: "2534", value: 41 },
{ label: "3549", value: 55 },
{ label: "5064", value: 68 },
{ label: "65+", value: 78 },
],
},
{
name: "E-books",
data: [
{ label: "1824", value: 48 },
{ label: "2534", value: 39 },
{ label: "3549", value: 30 },
{ label: "5064", value: 22 },
{ label: "65+", value: 12 },
],
},
],
},
claims: [
{
text: "Preference for print books increases with age across all groups shown.",
verdict: "supported",
explanation:
"Print preference rises at each age bracket: 32% → 41% → 55% → 68% → 78%. Every step increases, so this is directly supported.",
},
{
text: "Adults aged 65+ prefer print books because they find e-readers difficult to use.",
verdict: "neither",
explanation:
"The graph shows WHAT people prefer, not WHY. Difficulty with e-readers is a plausible explanation, but nothing in the data addresses reasons for preference.",
},
{
text: "E-books are more popular than print books among every age group under 35.",
verdict: "contradicted",
explanation:
'Ages 1824: E-books (48%) > Print (32%) — true. But ages 2534: E-books (39%) < Print (41%) — false. Since the claim says "every age group under 35," one counterexample is enough to contradict it. Always check ALL data points when a claim uses "every" or "all."',
},
],
},
{
title: "Line — Library Visits",
chart: {
type: "line",
title: "Monthly Library Visits per Capita (20182023)",
yLabel: "Visits per capita",
xLabel: "Year",
unit: "",
source: "American Library Association Annual Report",
series: [
{
name: "In-Person Visits",
data: [
{ label: "2018", value: 4.2 },
{ label: "2019", value: 4.0 },
{ label: "2020", value: 1.1 },
{ label: "2021", value: 2.3 },
{ label: "2022", value: 3.1 },
{ label: "2023", value: 3.4 },
],
},
{
name: "Digital Access",
data: [
{ label: "2018", value: 1.8 },
{ label: "2019", value: 2.1 },
{ label: "2020", value: 5.6 },
{ label: "2021", value: 4.9 },
{ label: "2022", value: 4.5 },
{ label: "2023", value: 4.3 },
],
},
],
},
claims: [
{
text: "Digital access surpassed in-person visits starting in 2020 and remained higher through 2023.",
verdict: "supported",
explanation:
"In 2020, Digital (5.6) > In-Person (1.1). This pattern continues: 2021 (4.9 > 2.3), 2022 (4.5 > 3.1), 2023 (4.3 > 3.4). Directly supported throughout every year from 2020 onward.",
},
{
text: "The pandemic caused the decline in in-person library visits in 2020.",
verdict: "neither",
explanation:
"The graph shows a sharp drop in in-person visits in 2020, but the data itself does not identify the CAUSE. While the pandemic is a plausible explanation, the graph provides no information about causes — only trends.",
},
{
text: "In-person visits in 2023 had fully recovered to pre-2020 levels.",
verdict: "contradicted",
explanation:
"2023 in-person visits = 3.4. Pre-2020 levels were 4.2 (2018) and 4.0 (2019). Since 3.4 < 4.0, visits had NOT fully recovered. Directly contradicted.",
},
],
},
];
const FUNCTION_WORDS = [
{
label: "Positive / Supportive",
content:
"advance, affirm, bolster, defend, exemplify, illustrate, provide evidence for, substantiate, praise, propose, support",
},
{
label: "Negative / Critical",
content:
"challenge, contradict, criticize, debate, deny, dismiss, question, refute, undermine, warn, raise concerns about",
},
{
label: "Neutral — Describe",
content:
"characterize, convey, depict, describe, discuss, dramatize, portray, present, represent, show, trace",
},
{
label: "Neutral — Emphasize",
content:
"call attention to, emphasize, highlight, reinforce, reiterate, underscore",
},
{
label: "Neutral — Explain",
content:
"account for, clarify, define, explicate, explain, identify, indicate, specify",
},
{
label: "Neutral — Analyze",
content:
"analyze, consider, develop, explore, hypothesize, imply, reflect on, speculate, suggest",
},
{
label: "Compare / Contrast",
content: "compare, contrast, distinguish between, draw a parallel between",
},
{
label: "Qualify",
content:
"acknowledge, concede, downplay, minimize, qualify — note: qualify means limit or refine, not verify",
},
];
const WRONG_ANSWER_PATTERNS = [
{
label: "Off-topic",
content:
"Mentions something real but not discussed in the passage or relevant lines",
},
{
label: "Too broad",
content:
"Passage discusses one specific thing; answer generalizes to the whole category",
},
{
label: "Too extreme",
content:
"Includes words like always, never, only, completely, impossible, proven",
},
{
label: "Half-right, half-wrong",
content:
"Contains accurate language but also one incorrect element — one wrong word kills the whole answer",
},
{
label: "Could be true",
content:
"Plausible or factually accurate, but not supported by the specific passage",
},
{
label: "Passage-true but not local",
content:
"Accurate for the passage as a whole but not for the specific lines referenced",
},
];
const EBRWTextStructurePurposeLesson: 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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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-fuchsia-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
${isActive ? "bg-fuchsia-600 text-white" : isPast ? "bg-fuchsia-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-fuchsia-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="Pronouns &amp; Main Point"
icon={BookOpen}
/>
<SectionMarker
index={1}
title="Old/New &amp; Passage Types"
icon={Star}
/>
<SectionMarker
index={2}
title="Function &amp; Evidence"
icon={BarChart3}
/>
<SectionMarker index={3} title="Passage Hunter" icon={Lightbulb} />
<SectionMarker index={4} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 w-full mx-auto">
{/* ── SECTION 0: PRONOUNS & MAIN POINT ── */}
<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-fuchsia-100 text-fuchsia-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4 w-fit">
Craft &amp; Structure Domain 1
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Text Structure &amp; Purpose
</h2>
<p className="text-lg text-slate-500 mb-8">
Understanding not just what a passage says but how it is organized
and why the two foundational skills are tracking references and
finding the main point.
</p>
{/* Pronouns & Compression Nouns */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
Pronouns &amp; Compression Nouns
</h3>
<p className="text-sm text-slate-700">
SAT passages routinely use pronouns (it, they, this, that, these)
and abstract "compression" nouns (phenomenon, claim, notion,
approach) to refer back to ideas already introduced. Inability to
trace these references is a major source of comprehension error.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-white border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Pronoun Rules
</p>
<ul className="space-y-1">
{[
"Always back up to find the referent — it may be in a previous sentence or paragraph.",
"Match singular pronouns to singular referents; plural pronouns to plural referents.",
"When multiple nouns of the same type appear, a pronoun becomes ambiguous — re-read carefully.",
"It and its can refer to different nouns in the same paragraph — track each separately.",
].map((r) => (
<li
key={r}
className="flex items-start gap-2 text-xs text-slate-600"
>
<span className="text-fuchsia-500 shrink-0 mt-0.5">
</span>
{r}
</li>
))}
</ul>
</div>
<div className="bg-white border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Compression Nouns
</p>
<p className="text-xs text-slate-600 mb-2">
Words like{" "}
<em>
phenomenon, observation, claim, assertion, notion, approach,
finding
</em>{" "}
compress an entire idea into one phrase.
</p>
<ul className="space-y-1">
{[
'"this + noun": "this enhanced convenience" compresses the entire previous sentence.',
'"the former / the latter": former = first mentioned; latter = last mentioned.',
"The referent may be 35 lines before the compression noun — always read backwards.",
].map((r) => (
<li
key={r}
className="flex items-start gap-2 text-xs text-slate-600"
>
<span className="text-fuchsia-500 shrink-0 mt-0.5">
</span>
{r}
</li>
))}
</ul>
</div>
</div>
<div className="bg-fuchsia-100 border border-fuchsia-200 rounded-xl p-4">
<p className="text-sm text-slate-700">
<span className="font-bold text-fuchsia-800">Strategy: </span>
When you encounter a pronoun or compression noun, stop. Back up
to the beginning of the sentence where the referent was
introduced. Never start reading mid-sentence when searching for
a referent.
</p>
</div>
</div>
{/* Topics & Main Point */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-slate-900">
Identifying Topics and Main Points
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4">
<p className="font-bold text-slate-700 text-sm mb-1">
The Topic
</p>
<p className="text-xs text-slate-600 mb-2">
The primary subject or focus of the passage the person,
thing, or idea it centers on. Typically appears in the first
sentence or two.
</p>
<p className="text-xs text-slate-500 italic">
Correct answers to main-idea questions always refer to the
topic. Many wrong answers are wrong specifically because they
are off-topic.
</p>
</div>
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-1">
The Main Point
</p>
<p className="text-xs text-slate-600 mb-2">
The primary argument the author wants to convey. It answers:{" "}
<em>so what?</em> It tells us why the topic matters.
</p>
<div className="flex items-center gap-2 mt-2">
<span className="text-xs bg-fuchsia-600 text-white px-2 py-0.5 rounded font-bold">
Topic + So What? = Main Point
</span>
</div>
</div>
</div>
<div className="bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-slate-700 text-sm mb-2">
Where to Find the Main Point
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{[
"Most commonly in the first sentence or two",
"Often restated or reinforced in the last sentence",
"Signaled by: the point is, key, central, essential, significant, or italics",
"Often located near dashes, colons, or major transitions (however, therefore, in fact)",
].map((p) => (
<div
key={p}
className="flex items-start gap-2 text-xs text-slate-600"
>
<span className="text-fuchsia-500 shrink-0 mt-0.5"></span>
{p}
</div>
))}
</div>
</div>
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3">
<p className="text-xs text-slate-600">
<span className="font-bold text-slate-800">
What to write down:{" "}
</span>
A 36 word compressed summary on scratch paper. Example: "New
evidence overturns Clovis theory." This prevents you from
forgetting the point when navigating confusing answer choices.
</p>
</div>
</div>
</section>
{/* ── SECTION 1: OLD/NEW & PASSAGE TYPES ── */}
<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-2">
Old/New &amp; Passage Types
</h2>
<p className="text-lg text-slate-500 mb-8">
The most important structural pattern in SAT science passages and
how main points differ across fiction, non-fiction, and poetry.
</p>
{/* Old/New Framework */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
The Old Idea / New Idea Framework
</h3>
<p className="text-sm text-slate-700">
One of the most important structural patterns in SAT passages:
people used to believe X, but new evidence shows Y is actually
true. Recognizing old-idea signals allows you to predict the main
point before the author states it.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-red-700 text-sm mb-2">
Old Idea Signal Phrases
</p>
<ul className="space-y-1">
{[
'"Some / many (scientists) believe..." — "some" especially implies the author disagrees',
'"It is commonly thought that..." / "Accepted wisdom holds that..."',
'"Traditionally it was believed..." / "For decades, scientists thought..."',
'"In the past..."',
].map((s) => (
<li
key={s}
className="text-xs text-slate-600 flex items-start gap-1"
>
<span className="shrink-0"></span>
{s}
</li>
))}
</ul>
</div>
<div className="bg-white border border-slate-200 rounded-xl p-4">
<p className="font-bold text-green-700 text-sm mb-2">
New Idea Signal Phrases
</p>
<ul className="space-y-1">
{[
"However, but in fact, actually, in reality",
'"It now seems / researchers now think..."',
'"Recently, it has been found that..." / "New research suggests..."',
'"But is it really the case that...?" (rhetorical question)',
].map((s) => (
<li
key={s}
className="text-xs text-slate-600 flex items-start gap-1"
>
<span className="shrink-0"></span>
{s}
</li>
))}
</ul>
</div>
</div>
<div className="bg-fuchsia-900 text-white rounded-xl p-4">
<p className="font-bold text-sm mb-1">Strategy</p>
<p className="text-xs text-fuchsia-100">
Write <em>Old = [3-word summary] | New = [3-word summary]</em>{" "}
on scratch paper before answering questions. The main point is
almost always the NEW idea. If you identify the old idea, you
can predict the new idea before reading it.
</p>
</div>
</div>
{/* Passage Types */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-slate-900">
Main Point for Different Passage Types
</h3>
<div className="space-y-3">
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4 card-tilt">
<p className="font-bold text-slate-700 text-sm mb-1">
Non-Fiction (Science, Social Science, Humanities)
</p>
<p className="text-xs text-slate-600">
Main points are usually stated directly, often in the first
and last sentences. The old idea / new idea framework applies
most frequently here.
</p>
</div>
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4 card-tilt">
<p className="font-bold text-slate-700 text-sm mb-2">
Fiction Passages
</p>
<p className="text-xs text-slate-600 mb-2">
Fiction passages do not make arguments but focus on particular
traits, qualities, or situations of characters or places. Key
information tends to appear at the beginning and end of the
excerpt.
</p>
<ul className="space-y-1">
{[
"Do not interpret or speculate beyond what is literally stated.",
"Focus on the literal actions of characters and the specific words describing them.",
"Pay attention to unusual punctuation, strong language, and major transitions.",
"Characters' speech often signals the key idea, especially near the end.",
].map((r) => (
<li
key={r}
className="flex items-start gap-2 text-xs text-slate-500"
>
<span className="text-fuchsia-400 shrink-0 mt-0.5">
</span>
{r}
</li>
))}
</ul>
</div>
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4 card-tilt">
<p className="font-bold text-slate-700 text-sm mb-2">
Poetry Passages
</p>
<p className="text-xs text-slate-600 mb-2">
The literal meaning may be conveyed indirectly through images
or metaphors, but students must stay within the bounds of the
poem and avoid over-interpretation.
</p>
<ul className="space-y-1">
{[
"Identify the two main sections (often divided at the midpoint).",
"Determine what each section is about at the most literal level.",
"Look for the contrast or development between sections — that relationship usually contains the main idea.",
"Avoid attributing complex philosophical meaning unless unambiguously supported by specific words.",
].map((r) => (
<li
key={r}
className="flex items-start gap-2 text-xs text-slate-500"
>
<span className="text-fuchsia-400 shrink-0 mt-0.5">
</span>
{r}
</li>
))}
</ul>
</div>
</div>
</div>
</section>
{/* ── SECTION 2: FUNCTION, EVIDENCE & GRAPHS ── */}
<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-2">
Function, Evidence &amp; Graphs
</h2>
<p className="text-lg text-slate-500 mb-8">
Three advanced question types all require identifying the claim
precisely before answering, then playing positive/negative to
eliminate.
</p>
{/* Reading for Function */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
Reading for Function
</h3>
<p className="text-sm text-slate-700">
Function questions ask not <em>what</em> a sentence says but{" "}
<em>why</em> it says it what rhetorical role that content plays.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-white border border-fuchsia-100 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Main Point vs. Primary Purpose
</p>
<div className="space-y-2">
<div>
<p className="text-xs font-bold text-fuchsia-700">
Main Point
</p>
<p className="text-xs text-slate-600">
The central CLAIM what the author is arguing. Usually
stated directly.
</p>
</div>
<div>
<p className="text-xs font-bold text-fuchsia-700">
Primary Purpose
</p>
<p className="text-xs text-slate-600">
The rhetorical GOAL what the author is doing
(explaining, emphasizing, arguing). Often more general or
abstract.
</p>
</div>
</div>
</div>
<div className="bg-white border border-fuchsia-100 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
5-Step Method for Function Questions
</p>
<ol className="space-y-1">
{[
"Determine whether the question asks about a sentence or the whole passage.",
"Read one to two sentences before and after the underlined portion.",
"Ask: What point is this sentence supporting?",
"Play positive/negative — eliminate answers with the wrong charge.",
"Match the remaining choice to what the passage is actually doing.",
].map((step, i) => (
<li
key={i}
className="flex items-start gap-2 text-xs text-slate-600"
>
<span className="w-4 h-4 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-xs shrink-0 mt-0.5">
{i + 1}
</span>
{step}
</li>
))}
</ol>
</div>
</div>
<RevealCardGrid
cards={FUNCTION_WORDS}
columns={2}
accentColor="fuchsia"
/>
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
<p className="text-xs text-red-800">
Eliminate extreme verbs immediately:{" "}
<em>prove, celebrate, condemn, discredit, mock, scoff at</em>.
Passages almost never contain sufficient evidence to
definitively prove or disprove anything.
</p>
</div>
</div>
{/* Text Completions & Support/Undermine */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-slate-900">
Text Completions &amp; Support / Undermine
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Text Completions
</p>
<p className="text-xs text-slate-600 mb-2">
All necessary information is present in the passage. The
inference required is small resist speculation.
</p>
<ol className="space-y-1">
{[
"Carefully read the claim just before the blank.",
"Restate it in your own words (36 words).",
"Work out the single most logical implication.",
"Find the answer that matches — possibly rephrased.",
].map((s, i) => (
<li key={i} className="flex gap-2 text-xs text-slate-700">
<span className="w-4 h-4 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-xs shrink-0">
{i + 1}
</span>
{s}
</li>
))}
</ol>
</div>
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-2">
Support &amp; Undermine
</p>
<p className="text-xs text-slate-600 mb-2">
Students must identify statements that strengthen or weaken a
given claim. These tend to appear among the hardest questions.
</p>
<ol className="space-y-1">
{[
"Identify and paraphrase the claim precisely.",
"Predict upfront what kind of information would support or weaken it.",
"Match your prediction — wrong answers will be off-topic or address a different claim.",
].map((s, i) => (
<li key={i} className="flex gap-2 text-xs text-slate-700">
<span className="w-4 h-4 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-xs shrink-0">
{i + 1}
</span>
{s}
</li>
))}
</ol>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-green-50 border border-green-200 rounded-xl p-4">
<p className="font-bold text-green-800 text-sm mb-1">Support</p>
<p className="text-xs text-slate-600">
Correct answer provides evidence consistent with the claim
same entities or phenomena, results in the same direction. Not
off-topic, not tangential.
</p>
</div>
<div className="bg-red-50 border border-red-200 rounded-xl p-4">
<p className="font-bold text-red-800 text-sm mb-1">Undermine</p>
<p className="text-xs text-slate-600">
Correct answer contradicts or weakens the claim. Most common
pattern: claim says "X causes Y"; correct answer shows X is
present but Y did not follow.
</p>
</div>
</div>
</div>
{/* Graphs & Answer Patterns */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal stagger-2">
<h3 className="text-lg font-bold text-fuchsia-900">
Graphs, Charts &amp; Wrong Answer Patterns
</h3>
<p className="text-sm text-slate-700">
Graph-based questions follow predictable patterns and can often be
answered without extensively examining the graph.
</p>
<div className="space-y-2">
{[
{
step: 1,
text: "Read the passage first — especially the last sentence. This states the claim.",
},
{
step: 2,
text: "Read the question carefully to identify the exact focus.",
},
{
step: 3,
text: "Eliminate answer choices using the passage before looking at the graph.",
},
{
step: 4,
text: "Confirm the remaining option against the graph.",
},
].map((s) => (
<div
key={s.step}
className="flex gap-3 bg-white border border-fuchsia-100 rounded-lg p-3"
>
<span className="w-5 h-5 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{s.step}
</span>
<p className="text-xs text-slate-700">{s.text}</p>
</div>
))}
</div>
<RevealCardGrid
cards={WRONG_ANSWER_PATTERNS}
columns={2}
accentColor="fuchsia"
/>
</div>
{/* Graph Practice */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4">
<h3 className="text-lg font-bold text-slate-900">
Graph Practice Evaluate Claims Against Data
</h3>
<p className="text-sm text-slate-600">
Apply the 4-step process: read the claim, extract criteria, check
the graph, and judge whether the data supports, contradicts, or
neither proves the claim.
</p>
<DataClaimWidget
exercises={GRAPH_EXERCISES}
accentColor="fuchsia"
/>
</div>
<div className="bg-fuchsia-900 text-white rounded-2xl p-5 mb-8 scroll-reveal-scale golden-rule-glow">
<p className="font-bold mb-1">Golden Rule</p>
<p className="text-sm text-fuchsia-100">
The main point is almost always the sentence that introduces NEW
information look for signal words like "however," "but," or "in
contrast." The OLD information (background, history, common
belief) is context, not the point. For function questions: play
positive/negative first, then match the function verb to what the
passage is actually doing.
</p>
</div>
</section>
{/* ── SECTION 3: WIDGET ── */}
<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-2">
Passage Hunter
</h2>
<p className="text-lg text-slate-500 mb-8">
Click the sentence that best answers each question. Look for the "So
What" and purpose signal words.
</p>
<EvidenceHunterWidget
exercises={EVIDENCE_EXERCISES}
accentColor="fuchsia"
/>
</section>
{/* ── SECTION 4: PRACTICE ── */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Questions
</h2>
{TEXT_STRUCTURE_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
{TEXT_STRUCTURE_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-fuchsia-900 text-white font-bold rounded-full hover:bg-fuchsia-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWTextStructurePurposeLesson;

File diff suppressed because it is too large Load Diff

View File

@ -241,7 +241,7 @@ const EBRWVerbsLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker index={0} title="Verb Anatomy" icon={BookOpen} /> <SectionMarker index={0} title="Verb Anatomy" icon={BookOpen} />
<SectionMarker <SectionMarker

View File

@ -185,7 +185,7 @@ const EBRWVocabMeaningLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker
index={0} index={0}

View File

@ -185,7 +185,7 @@ const EBRWVocabPreciseLesson: React.FC<LessonProps> = ({ onFinish }) => {
return ( return (
<div className="flex flex-col lg:flex-row min-h-screen"> <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 lg:bg-transparent z-0 hidden lg:block"> <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"> <nav className="space-y-2 pt-6">
<SectionMarker <SectionMarker
index={0} index={0}

View File

@ -0,0 +1,902 @@
import React, { useRef, useState, useEffect } from "react";
import { Check, BookOpen, Lightbulb, Zap, Star, Target } from "lucide-react";
import { PracticeFromDataset } from "../../../components/lessons/LessonShell";
import {
WORDS_CONTEXT_EASY,
WORDS_CONTEXT_MEDIUM,
} from "../../../data/rw/words-in-context";
import ContextEliminationWidget, {
type VocabExercise,
} from "../../../components/lessons/ContextEliminationWidget";
import RevealCardGrid from "../../../components/lessons/RevealCardGrid";
import useScrollReveal from "../../../components/lessons/useScrollReveal";
interface LessonProps {
onFinish?: () => void;
}
const VOCAB_EXERCISES: VocabExercise[] = [
{
sentence:
"The documentary filmmaker hoped to ______ audiences with her unflinching portrayal of poverty, prompting them to advocate for social change.",
word: "______",
question: "Which word most logically completes the sentence?",
options: [
{
id: "a",
definition: "entertain — to provide amusement",
isCorrect: false,
elimReason:
"The context signals a serious goal (advocacy, social change), not mere amusement.",
},
{
id: "b",
definition: "galvanize — to shock or excite into taking action",
isCorrect: true,
elimReason: "",
},
{
id: "c",
definition: "placate — to make less angry or upset",
isCorrect: false,
elimReason:
"Contradicts the goal of prompting advocacy; placating suggests calming, not motivating.",
},
{
id: "d",
definition: "perplex — to confuse someone greatly",
isCorrect: false,
elimReason:
"Confusing audiences would undermine the filmmaker's goal of advocacy.",
},
],
},
{
sentence:
"Though often described as a recluse, the novelist was in fact quite ______, hosting weekly salons attended by scientists, poets, and politicians.",
word: "______",
question: "Which word most logically completes the sentence?",
options: [
{
id: "a",
definition: "gregarious — fond of company; sociable",
isCorrect: true,
elimReason: "",
},
{
id: "b",
definition: "reticent — not revealing thoughts or feelings",
isCorrect: false,
elimReason:
'Reticent reinforces "recluse" rather than contrasting with it; the word "though" signals a contradiction.',
},
{
id: "c",
definition: "pensive — engaged in deep thought",
isCorrect: false,
elimReason:
"Being thoughtful does not explain weekly social gatherings.",
},
{
id: "d",
definition: "fastidious — very attentive to detail",
isCorrect: false,
elimReason:
"Attention to detail is unrelated to the sociability implied by hosting salons.",
},
],
},
{
sentence:
"The economist's forecast proved remarkably ______: every indicator she had cited came to pass within two years.",
word: "______",
question: "Which word most logically completes the sentence?",
options: [
{
id: "a",
definition: "optimistic — hopeful about the future",
isCorrect: false,
elimReason:
"Optimistic describes an attitude, not the accuracy of a forecast.",
},
{
id: "b",
definition: "prescient — having knowledge of events before they occur",
isCorrect: true,
elimReason: "",
},
{
id: "c",
definition: "contentious — causing disagreement",
isCorrect: false,
elimReason:
'The colon and "every indicator came to pass" signals accuracy, not controversy.',
},
{
id: "d",
definition: "redundant — not or no longer needed; superfluous",
isCorrect: false,
elimReason:
"Redundancy contradicts the idea that every prediction proved true.",
},
],
},
];
const CLAIM_VERBS = [
{
label: "Make a claim",
words: "advance, hypothesize, posit, proffer, speculate",
},
{ label: "Support a claim", words: "bolster, buttress, substantiate" },
{
label: "Question a claim",
words: "rebut, refute, ambivalence, skepticism",
},
{ label: "Draw a conclusion", words: "infer, surmise" },
{ label: "Large amount", words: "multitude, plethora, profusion" },
{ label: "Small amount", words: "dearth, paucity" },
{ label: "Different/diverse", words: "disparate, eclectic, heterogeneous" },
{ label: "Noticeable/striking", words: "conspicuous, distinctive, salient" },
{ label: "Inborn", words: "inherent, innate, intrinsic" },
];
const SCIENCE_TERMS = [
{ word: "empirical", def: "Relying on hard, observable data" },
{ word: "hierarchy", def: "System of rank; opposite of egalitarian" },
{ word: "indigenous", def: "Native to a region" },
{ word: "inhibit", def: "To prevent or impede a response" },
{ word: "predisposed", def: "Having a tendency toward something" },
{ word: "replicate", def: "To repeat an experiment with same results" },
{ word: "stimulus", def: "Something that provokes a specific response" },
{ word: "symbiotic", def: "Beneficial interaction between two organisms" },
{ word: "taxonomy", def: "A classification system" },
{ word: "velocity", def: "Speed" },
];
const ALL_VOCAB_KEYS = [
...CLAIM_VERBS.map((v) => v.label),
...SCIENCE_TERMS.map((t) => t.word),
];
const SECOND_MEANINGS = [
{ label: "arrest", content: "To stop (not just apprehend a criminal)" },
{
label: "assume",
content: "To take on responsibility for; to acquire a new position",
},
{
label: "bent",
content:
"A liking or natural tendency toward something (synonym: proclivity)",
},
{ label: "capacity", content: "Ability" },
{
label: "channel",
content: "To direct energy or resources toward a specific purpose",
},
{ label: "check", content: "To restrain, control, or reduce" },
{ label: "coin", content: "To invent (e.g., coin a phrase)" },
{ label: "compromise", content: "To endanger or make vulnerable" },
{
label: "discriminating",
content: "Able to make fine distinctions (positive word)",
},
{ label: "doctor", content: "To tamper with" },
{
label: "economy / economical",
content: "Thrift; using few words or resources (not about money)",
},
{
label: "exploit",
content: "To make use of (no negative connotation required)",
},
{ label: "facility", content: "Ease; ability to do something effortlessly" },
{ label: "fluid", content: "Able to change shape; not rigid" },
{ label: "foil", content: "To put a stop to; to thwart" },
{ label: "harbor", content: "To hold or possess (e.g., harbor a belief)" },
{
label: "plastic",
content: "Malleable; able to be changed (as in brain plasticity)",
},
{ label: "reserve / reservations", content: "To hold off; misgivings" },
{ label: "sound", content: "Firm, stable, reliable, valid" },
{ label: "static", content: "Unchanging; in a state of stasis" },
{
label: "table / shelve",
content: "To reject or discard an idea or proposal",
},
{ label: "uniform", content: "Constant; unvarying" },
{ label: "upset", content: "To interfere with an expected outcome" },
];
const NEGATIVE_LOOKING = [
{
label: "critic / criticism",
content:
"A person who writes commentary, positive or negative; not inherently hostile",
},
{
label: "discerning",
content: "Perceptive; able to make fine distinctions",
},
{ label: "ineffable", content: "Beyond words; sublime; indescribable" },
{ label: "infallible", content: "Unable to be wrong; perfectly reliable" },
{ label: "ingenious", content: "Clever, brilliant" },
{ label: "ingenuous", content: "Naive (note: distinct from ingenious)" },
{ label: "inimitable", content: "Unique; one-of-a-kind" },
{ label: "innocuous", content: "Harmless" },
{ label: "invaluable", content: "Having immense value; priceless" },
{ label: "unassuming", content: "Modest" },
{ label: "unqualified", content: "Absolute (e.g., unqualified success)" },
];
const EBRWWordsInContextLesson: React.FC<LessonProps> = ({ onFinish }) => {
const [activeSection, setActiveSection] = useState(0);
const [revealedVocab, setRevealedVocab] = useState<Set<string>>(new Set());
const toggleVocab = (key: string) => {
setRevealedVocab((prev) => {
const next = new Set(prev);
if (next.has(key)) next.delete(key);
else next.add(key);
return next;
});
};
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());
}, []);
useScrollReveal();
const scrollToSection = (index: number) => {
setActiveSection(index);
sectionsRef.current[index]?.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-fuchsia-50" : "hover:bg-slate-50"}`}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0
${isActive ? "bg-fuchsia-600 text-white" : isPast ? "bg-fuchsia-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-fuchsia-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="Sentence Completions"
icon={BookOpen}
/>
<SectionMarker index={1} title="Context Signals" icon={Star} />
<SectionMarker
index={2}
title="Meaning in Context"
icon={Lightbulb}
/>
<SectionMarker index={3} title="Elimination Practice" icon={Target} />
<SectionMarker index={4} title="Practice Questions" icon={Zap} />
</nav>
</aside>
<div className="flex-1 lg:ml-64 md:p-12 max-w-4xl mx-auto">
{/* ── SECTION 0: SENTENCE COMPLETIONS ── */}
<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-fuchsia-100 text-fuchsia-700 px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider mb-4 w-fit">
Craft &amp; Structure Domain 1
</div>
<h2 className="text-4xl font-extrabold text-slate-900 mb-2">
Words in Context
</h2>
<p className="text-lg text-slate-500 mb-8">
Identify the signal, determine the charge, predict your answer
before reading options context determines meaning, not the
dictionary.
</p>
{/* Two Question Subtypes */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
The Two Question Subtypes
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[
{
num: "A",
rule: "Sentence Completions",
stem: '"Which choice completes the text with the most logical and precise word or phrase?"',
desc: "A blank must be filled. Answer choices include challenging academic vocabulary (epitomize, succinct, anomaly). Requires both contextual reasoning AND knowledge of word meanings.",
},
{
num: "B",
rule: "Meaning in Context",
stem: '"As used in the text, what does the word X most nearly mean?"',
desc: "A familiar word is used in a non-standard definition. The dictionary meaning is VERY LIKELY to appear as a wrong answer — treat it as a red flag, not a confirmation.",
},
].map((r) => (
<div
key={r.num}
className="bg-white border border-fuchsia-200 rounded-xl p-4 card-tilt"
>
<div className="flex items-center gap-2 mb-2">
<span className="w-6 h-6 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-xs font-bold shrink-0">
{r.num}
</span>
<span className="font-bold text-fuchsia-900 text-sm">
{r.rule}
</span>
</div>
<p className="text-xs text-slate-400 italic mb-2">{r.stem}</p>
<p className="text-xs text-slate-600">{r.desc}</p>
</div>
))}
</div>
</div>
{/* When to Study Vocabulary */}
<div className="bg-white border border-slate-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-slate-900">
When to Study Vocabulary
</h3>
<p className="text-sm text-slate-600">
If you are already scoring above 90% on Words in Context
questions, vocabulary study has diminishing returns. Focus on
speed and elimination instead. If you are scoring below 70%,
targeted vocabulary building especially roots, prefixes, and the
academic words below is the single highest-ROI investment you
can make for this question type.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-1">
High Scorers (90%+)
</p>
<p className="text-xs text-slate-600">
Focus on elimination speed, second meanings, and advanced
context clue patterns. Vocabulary drills are unlikely to gain
points.
</p>
</div>
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-1">
Building Scorers (below 70%)
</p>
<p className="text-xs text-slate-600">
Study the Academic Vocabulary and Common Second Meanings
tables below. Learn 10 new words per week using flashcards
with context sentences, not isolated definitions.
</p>
</div>
</div>
</div>
{/* 4-Step Process */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-2">
<h3 className="text-lg font-bold text-slate-900">
The 4-Step Process for Sentence Completions
</h3>
<div className="space-y-3">
{[
{
step: 1,
title: "Read the entire passage",
desc: "Identify key words or phrases. Consider the missing word in context of the full passage — focusing only on the blank line is a common error that leads to missing crucial information elsewhere.",
},
{
step: 2,
title: "Plug in your own word",
desc: "If a word comes to mind, write it down. This reduces error by establishing what the blank requires before looking at options. Spend no more than a few seconds on this. If you are not certain what belongs, skip this step entirely — guessing a filler word when uncertain creates misdirection.",
},
{
step: 3,
title: "Play positive/negative",
desc: "Determine whether the blank requires a positive, negative, or neutral word and write + or on scratch paper. Then eliminate any answer choice that contradicts the charge. Go through options in order (A through D) to maintain systematic thinking and reduce careless error.",
},
{
step: 4,
title: "Plug in remaining options and compare",
desc: "Choose the best fit. Focus on what a word means, not how it sounds — a word may seem odd in context yet be the precise match required.",
},
].map((s) => (
<div
key={s.step}
className="flex gap-3 bg-fuchsia-50 border border-fuchsia-100 rounded-xl p-4"
>
<span className="w-7 h-7 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-sm font-bold shrink-0">
{s.step}
</span>
<div>
<p className="font-bold text-slate-800 text-sm">
{s.title}
</p>
<p className="text-xs text-slate-500 mt-0.5">{s.desc}</p>
</div>
</div>
))}
</div>
</div>
</section>
{/* ── SECTION 1: CONTEXT SIGNALS ── */}
<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-2">
Context Signals
</h2>
<p className="text-lg text-slate-500 mb-8">
Signal words tell you exactly what direction and charge the blank
must have. Identify the signal before looking at any answer choice.
</p>
{/* 3 Context Clue Signal Types */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
The 3 Context Clue Signal Types
</h3>
<div className="space-y-3">
{[
{
type: "Type 1: Continuers",
color: "bg-green-50 border-green-200",
badge: "bg-green-600",
words:
"also, and, as well as, furthermore, in addition, just as, likewise, moreover, not only...but also, similarly",
meaning:
"The sentence continues in the same direction. The blank must have a similar meaning or charge to the surrounding context.",
ex: 'Soil is poor and resources are ___. The continuer "and" links the blank to "poor" — the blank must also be negative, meaning something like scarce or limited.',
},
{
type: "Type 2: Cause-and-Effect",
color: "bg-blue-50 border-blue-200",
badge: "bg-blue-600",
words:
"as a result, because, consequently, therefore, thus — and punctuation marks such as colons and dashes",
meaning:
"One idea causes or explains another. The blank must fit logically as either the cause or the effect of nearby information.",
ex: 'Astronauts required mental evaluation because the ___ danger was as important as the physiological one. "Because" links the blank to mental evaluation — the blank must mean psychological.',
},
{
type: "Type 3: Contradictors",
color: "bg-red-50 border-red-200",
badge: "bg-red-600",
words:
"although, but, despite, for all (= despite), however, in contrast, in spite of, meanwhile, nevertheless, on the other hand, though, unlike, whereas, while",
meaning:
"The sentence shifts direction. The blank must have the opposite meaning or charge from what precedes or follows the contradictor.",
ex: 'The Afar Triangle seems quite ___. However, its appearance is deceptive, obscuring fiery lava just beneath. "However" signals contrast with fiery lava — the blank must mean calm or peaceful.',
},
].map((s) => (
<div
key={s.type}
className={`border rounded-xl p-4 ${s.color}`}
>
<div className="flex items-center gap-2 mb-1">
<span
className={`px-2 py-0.5 rounded-full text-white text-xs font-bold ${s.badge}`}
>
{s.type}
</span>
</div>
<p className="text-xs text-slate-500 italic mb-1">
{s.words}
</p>
<p className="text-sm text-slate-700 mb-2">{s.meaning}</p>
<div className="bg-white/70 rounded-lg p-2">
<p className="text-xs text-slate-600 italic">
<span className="font-bold not-italic text-slate-700">
Example:{" "}
</span>
{s.ex}
</p>
</div>
</div>
))}
</div>
</div>
{/* Advanced Patterns */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-slate-900">
Advanced Sentence Structure Patterns
</h3>
<div className="space-y-3">
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-1">
Parallel Structure
</p>
<p className="text-xs text-slate-600 mb-2">
When a sentence uses constructions on either side of a paired
word (both...and, not only...but also), the items must be
grammatically parallel. This structure tells you what form the
blank must take.
</p>
<p className="text-xs text-slate-600 italic">
<span className="font-bold not-italic text-slate-700">
Example:{" "}
</span>
Praised for being both accessible and ___: while it captures
clarity, it does so without sacrificing subtlety or
complexity. Blank must parallel "accessible" something like{" "}
<em>nuanced</em> or <em>sophisticated</em>.
</p>
</div>
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-xl p-4">
<p className="font-bold text-fuchsia-800 text-sm mb-1">
Two Negatives Equal a Positive
</p>
<p className="text-xs text-slate-600 mb-2">
Watch for sentences in which the negation of a negative word
produces a positive overall meaning. Students see the negative
charge of individual words and miss the positive meaning they
create together.
</p>
<p className="text-xs text-slate-600 italic">
<span className="font-bold not-italic text-slate-700">
Example:{" "}
</span>
Other species play a less ___ role because their caterpillars
consume harmful insects. The overall idea is positive
(helpful). But "less + [something good] = negative." So the
blank must be a negative word like <em>detrimental</em>, not a
positive word like <em>beneficial</em>.
</p>
</div>
</div>
</div>
{/* Roots & Prefixes */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal stagger-2">
<h3 className="text-lg font-bold text-fuchsia-900">
Using Roots &amp; Prefixes
</h3>
<p className="text-sm text-slate-700">
Knowledge of Latin and Romance-language roots allows educated
guesses about unfamiliar words often more valuable than
memorizing definitions because it applies to words never seen
before.
</p>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{[
["a-", "not (anomaly = unique)"],
["de-", "not/away from (detrimental = harmful)"],
["bene-", "good (beneficial = producing good)"],
["facil-", "easy (facility = ease)"],
].map(([prefix, meaning]) => (
<div
key={prefix}
className="bg-white border border-fuchsia-100 rounded-lg px-3 py-2"
>
<p className="font-bold text-fuchsia-700 text-sm">{prefix}</p>
<p className="text-xs text-slate-600">{meaning}</p>
</div>
))}
</div>
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
<p className="text-xs text-amber-800 font-bold mb-1">
Important Caveat
</p>
<p className="text-xs text-slate-600">
Some positive words have prefixes that ordinarily signal
negatives (discerning, innate, invaluable). These are
exceptions. In general, logical use of roots and prefixes
produces reliable results.
</p>
</div>
</div>
<div className="bg-fuchsia-900 text-white rounded-2xl p-5 mb-8 scroll-reveal-scale golden-rule-glow">
<p className="font-bold mb-1">Golden Rule Sentence Completions</p>
<p className="text-sm text-fuchsia-100">
Read the entire passage, identify the signal word (continuer /
cause-effect / contradictor), determine positive or negative
charge, plug in your own word, then eliminate by charge before
testing by meaning. Never look at answer choices first.
</p>
</div>
</section>
{/* ── SECTION 2: MEANING IN CONTEXT ── */}
<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-2">
Meaning in Context
</h2>
<p className="text-lg text-slate-500 mb-8">
Context determines meaning. A word means whatever the author uses it
to mean in that passage regardless of its dictionary definition.
</p>
{/* Three Strategies */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-fuchsia-900">
Three Strategies for Meaning-in-Context Questions
</h3>
<div className="space-y-3">
{[
{
num: 1,
title: "Substitute Your Own Word",
desc: "Use context to determine what word belongs, then find the answer choice that matches. This is the fastest approach for clear-context questions.",
},
{
num: 2,
title: "Plug In Each Answer Choice",
desc: "Replace the word in question with each option and test whether it fits the logic of the passage. Eliminate choices that produce illogical or grammatically awkward sentences.",
},
{
num: 3,
title: "Play Positive/Negative, Then Plug In",
desc: "Determine the charge of the blank, eliminate clearly opposite-charge answers, then test the remainder. Especially useful when meaning is less immediately clear.",
},
].map((s) => (
<div
key={s.num}
className="flex gap-3 bg-white border border-fuchsia-100 rounded-xl p-4"
>
<span className="w-7 h-7 rounded-full bg-fuchsia-600 text-white flex items-center justify-center text-sm font-bold shrink-0">
{s.num}
</span>
<div>
<p className="font-bold text-slate-800 text-sm">
{s.title}
</p>
<p className="text-xs text-slate-500 mt-0.5">{s.desc}</p>
</div>
</div>
))}
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4">
<p className="font-bold text-amber-900 text-sm mb-1">
Secondary Meaning Indicator
</p>
<p className="text-xs text-slate-700">
When all four answer choices are the same part of speech but one
is a word normally used as a different part of speech (e.g.,{" "}
<em>fluid</em> appearing as an adjective), this is a definitive
signal that a second meaning is being tested. The
unusual-looking option has an above-average chance of being
correct and should be examined first.
</p>
</div>
</div>
{/* Common Second Meanings */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal">
<h3 className="text-lg font-bold text-slate-900">
Common Second Meanings Master Reference
</h3>
<p className="text-sm text-slate-500">
These words frequently appear in Meaning-in-Context questions in
their secondary definitions. Tap each card to reveal.
</p>
<RevealCardGrid
cards={SECOND_MEANINGS}
columns={4}
accentColor="fuchsia"
/>
</div>
{/* Words That Look Negative But Are Not */}
<div className="bg-fuchsia-50 border border-fuchsia-200 rounded-2xl p-6 mb-8 space-y-4 scroll-reveal stagger-1">
<h3 className="text-lg font-bold text-fuchsia-900">
Words That Look Negative But Are Not
</h3>
<p className="text-sm text-slate-600">
These words trap students who eliminate them based on their prefix
or appearance. Tap each to see the actual definition.
</p>
<RevealCardGrid
cards={NEGATIVE_LOOKING}
columns={3}
accentColor="fuchsia"
/>
</div>
{/* Academic Vocabulary — Interactive */}
<div className="rounded-2xl p-6 mb-8 bg-white border border-slate-200 space-y-4 scroll-reveal stagger-2">
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold text-slate-900">
Academic Vocabulary to Know
</h3>
<div className="flex items-center gap-3">
<span className="text-xs text-slate-400">
{revealedVocab.size}/{ALL_VOCAB_KEYS.length} revealed
</span>
<button
onClick={() =>
setRevealedVocab(
revealedVocab.size >= ALL_VOCAB_KEYS.length
? new Set()
: new Set(ALL_VOCAB_KEYS),
)
}
className="text-xs font-bold text-fuchsia-600 hover:text-fuchsia-800 transition-colors"
>
{revealedVocab.size >= ALL_VOCAB_KEYS.length
? "Hide All"
: "Reveal All"}
</button>
</div>
</div>
<p className="text-sm text-slate-500">
Click each card to test yourself, then tap to reveal the answer.
</p>
<p className="font-bold text-fuchsia-800 text-sm">
Key Claim / Argument Verbs
</p>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{CLAIM_VERBS.map((v) => {
const isRevealed = revealedVocab.has(v.label);
return (
<button
key={v.label}
onClick={() => toggleVocab(v.label)}
className={`text-left rounded-xl p-3 border transition-all duration-200 ${isRevealed ? "bg-fuchsia-50 border-fuchsia-300 shadow-sm" : "bg-slate-50 border-slate-200 hover:border-fuchsia-300 hover:shadow-sm"}`}
>
<p className="font-bold text-fuchsia-700 text-xs mb-1">
{v.label}
</p>
{isRevealed ? (
<p className="text-xs text-slate-600 font-mono leading-relaxed">
{v.words}
</p>
) : (
<p className="text-xs text-slate-400 italic">
tap to reveal
</p>
)}
</button>
);
})}
</div>
<p className="font-bold text-fuchsia-800 text-sm mt-2">
Key Science &amp; Academic Terms
</p>
<div className="grid grid-cols-2 sm:grid-cols-5 gap-3">
{SCIENCE_TERMS.map((t) => {
const isRevealed = revealedVocab.has(t.word);
return (
<button
key={t.word}
onClick={() => toggleVocab(t.word)}
className={`text-left rounded-xl p-3 border transition-all duration-200 ${isRevealed ? "bg-fuchsia-50 border-fuchsia-300 shadow-sm" : "bg-slate-50 border-slate-200 hover:border-fuchsia-300 hover:shadow-sm"}`}
>
<p className="font-bold text-fuchsia-700 text-xs mb-1">
{t.word}
</p>
{isRevealed ? (
<p className="text-xs text-slate-600 leading-relaxed">
{t.def}
</p>
) : (
<p className="text-xs text-slate-400 italic">
tap to reveal
</p>
)}
</button>
);
})}
</div>
</div>
<div className="bg-fuchsia-900 text-white rounded-2xl p-5 mb-8 scroll-reveal-scale golden-rule-glow">
<p className="font-bold mb-1">Golden Rule Meaning in Context</p>
<p className="text-sm text-fuchsia-100">
Never rely on the dictionary definition. Context determines
meaning. The standard definition will very commonly appear as a
wrong answer treat its presence as a red flag, not a
confirmation.
</p>
</div>
</section>
{/* ── SECTION 3: WIDGET ── */}
<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-2">
Elimination Practice
</h2>
<p className="text-lg text-slate-500 mb-8">
Use the signal + your prediction to eliminate wrong answers one by
one.
</p>
<ContextEliminationWidget
exercises={VOCAB_EXERCISES}
accentColor="fuchsia"
/>
</section>
{/* ── SECTION 4: PRACTICE ── */}
<section
ref={(el) => {
sectionsRef.current[4] = el;
}}
className="min-h-screen flex flex-col justify-center mb-24"
>
<h2 className="text-4xl font-extrabold text-slate-900 mb-6">
Practice Questions
</h2>
{WORDS_CONTEXT_EASY.slice(0, 2).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
{WORDS_CONTEXT_MEDIUM.slice(0, 1).map((q) => (
<PracticeFromDataset key={q.id} question={q} color="fuchsia" />
))}
<div className="mt-8 text-center">
<button
onClick={onFinish}
className="px-6 py-3 bg-fuchsia-900 text-white font-bold rounded-full hover:bg-fuchsia-700 transition-colors"
>
Finish Lesson
</button>
</div>
</section>
</div>
</div>
);
};
export default EBRWWordsInContextLesson;

View File

@ -102,7 +102,7 @@ const LinesAnglesLesson: React.FC<LessonProps> = ({ onFinish }) => {
</nav> </nav>
</aside> </aside>
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto"> <div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* Section 1: Parallel Lines */} {/* Section 1: Parallel Lines */}
<section <section
ref={(el) => { ref={(el) => {

View File

@ -87,7 +87,7 @@ const SystemsEquationsLesson: React.FC<LessonProps> = ({ onFinish }) => {
</nav> </nav>
</aside> </aside>
<div className="flex-1 lg:ml-64 p-6 md:p-12 max-w-4xl mx-auto"> <div className="flex-1 lg:ml-64 md:p-12 max-w-full mx-auto">
{/* Section 1: Number of Solutions */} {/* Section 1: Number of Solutions */}
<section <section
ref={(el) => { ref={(el) => {

View File

@ -68,6 +68,8 @@ export interface LessonMetadata {
| "Triangle" | "Triangle"
| "Square" | "Square"
| "Chart" | "Chart"
| "GitMerge"
| "ArrowRightLeft"
| "Target" | "Target"
| "Calculator" | "Calculator"
| "TrendingUp" | "TrendingUp"

View File

@ -1,156 +1,102 @@
import type { LessonMetadata, QuizData } from "../types/lesson"; import type { LessonMetadata, QuizData } from "../types/lesson";
export const EBRW_LESSONS: LessonMetadata[] = [ export const EBRW_LESSONS: LessonMetadata[] = [
// --- INFORMATION AND IDEAS --- // --- CRAFT & STRUCTURE ---
{ {
id: "ebrw-main-idea", id: "ebrw-words-in-context",
title: "Main Idea Across Text Types", title: "Words in Context",
description: description:
"Identifying the author's central claim or purpose in nonfiction, literary fiction, drama, and poetry passages.", "Determine the meaning of a word or phrase as it is used in a passage by analyzing context, tone, and logical flow.",
iconName: "Target", iconName: "BookOpen",
color: "teal", color: "fuchsia",
category: "Information and Ideas", category: "Craft & Structure",
}, },
{ {
id: "ebrw-explicit-meaning", id: "ebrw-text-structure-purpose",
title: "Explicit Meaning & Key Details", title: "Text Structure & Purpose",
description: description:
"Locating directly stated information in nonfiction, literary fiction, and poetry — no inference required.", "Analyze how authors organize passages and why specific sentences, quotes, or examples are included.",
iconName: "BookOpen", iconName: "Layers",
color: "fuchsia",
category: "Craft & Structure",
},
{
id: "ebrw-cross-text-connections",
title: "Cross-Text Connections",
description:
"Compare two short texts and determine how their ideas relate, agree, or differ.",
iconName: "GitMerge",
color: "fuchsia",
category: "Craft & Structure",
},
// --- INFORMATION & IDEAS ---
{
id: "ebrw-central-ideas-details",
title: "Central Ideas & Details",
description:
"Identify the central claim or key supporting ideas in nonfiction, fiction, drama, or poetry passages.",
iconName: "Target",
color: "teal", color: "teal",
category: "Information and Ideas", category: "Information & Ideas",
}, },
{ {
id: "ebrw-inferences", id: "ebrw-inferences",
title: "Inferences & Text Completion", title: "Inferences",
description: description:
"Drawing logical conclusions and selecting sentences that most logically complete a passage's argument.", "Draw logical conclusions based on textual evidence, even when the answer is not directly stated.",
iconName: "BookOpen", iconName: "BookOpen",
color: "teal", color: "teal",
category: "Information and Ideas", category: "Information & Ideas",
}, },
{ {
id: "ebrw-graphic-displays", id: "ebrw-command-of-evidence",
title: "Reading Graphic Displays", title: "Command of Evidence",
description: description:
"Accurately reading bar graphs, line graphs, and tables — identifying trends, comparing data, and evaluating claims.", "Identify which evidence best supports a claim or conclusion made in a passage.",
iconName: "BarChart3", iconName: "BarChart3",
color: "teal", color: "teal",
category: "Information and Ideas", category: "Information & Ideas",
},
// --- CRAFT AND STRUCTURE ---
{
id: "ebrw-craft-structure",
title: "Craft & Structure: Author's Purpose",
description:
"Analyzing why an author includes specific details, quotes, or examples — understanding structural choices and rhetorical evaluation.",
iconName: "Layers",
color: "fuchsia",
category: "Craft and Structure",
},
{
id: "ebrw-vocab-precise",
title: "Vocabulary: Logical & Precise Word Choice",
description:
"Selecting the most precise or logically appropriate word to complete a sentence — matching logic, tone, and strength.",
iconName: "Calculator",
color: "fuchsia",
category: "Craft and Structure",
},
{
id: "ebrw-vocab-meaning",
title: "Vocabulary: Most Nearly Means",
description:
"Interpreting words and phrases as used in context — not their dictionary definition. Context always overrides the textbook.",
iconName: "BookOpen",
color: "fuchsia",
category: "Craft and Structure",
},
// --- EXPRESSION OF IDEAS ---
{
id: "ebrw-expression-ideas",
title: "Expression of Ideas: Synthesis",
description:
"Revising and combining information from notes or two texts, and incorporating evidence to support a specific claim.",
iconName: "TrendingUp",
color: "rose",
category: "Expression of Ideas",
},
{
id: "ebrw-transitions",
title: "Transitions: Conjunctive Adverbs",
description:
"Choosing the right logical connector (however, therefore, furthermore) to improve the effectiveness of written expression.",
iconName: "Grid",
color: "rose",
category: "Expression of Ideas",
}, },
// --- STANDARD ENGLISH CONVENTIONS --- // --- STANDARD ENGLISH CONVENTIONS ---
{ {
id: "ebrw-commas", id: "ebrw-boundaries",
title: "Punctuation: Commas", title: "Boundaries",
description: description:
"Comma rules for FANBOYS, introductory elements, nonessential information, and lists — the most-tested punctuation on the SAT.", "Use punctuation correctly to separate clauses and sentences, including commas, semicolons, and colons.",
iconName: "BookOpen", iconName: "BookOpen",
color: "purple", color: "purple",
category: "Standard English Conventions", category: "Standard English",
}, },
{ {
id: "ebrw-semicolons-colons", id: "ebrw-form-structure-sense",
title: "Punctuation: Semicolons & Colons", title: "Form, Structure & Sense",
description: description:
"Using semicolons to join independent clauses and colons to introduce lists or explanations after a complete sentence.", "Ensure sentences are grammatically correct and logically structured, including agreement, modifiers, and verb form.",
iconName: "BookOpen",
color: "purple",
category: "Standard English Conventions",
},
{
id: "ebrw-dashes-apostrophes",
title: "Punctuation: Dashes, Apostrophes & More",
description:
"Em dashes for emphasis, apostrophes for possession and contractions, end marks, and avoiding unnecessary punctuation.",
iconName: "BookOpen",
color: "purple",
category: "Standard English Conventions",
},
{
id: "ebrw-subject-verb",
title: "Agreement: SubjectVerb",
description:
"Finding the true subject and matching the verb — not misled by prepositional phrases, interrupting clauses, or inverted order.",
iconName: "Scale", iconName: "Scale",
color: "purple", color: "purple",
category: "Standard English Conventions", category: "Standard English",
},
// --- EXPRESSION OF IDEAS ---
{
id: "ebrw-transitions",
title: "Transitions",
description:
"Choose the transition word or phrase that best connects ideas and improves the flow of a passage.",
iconName: "ArrowRightLeft",
color: "rose",
category: "Expression of Ideas",
}, },
{ {
id: "ebrw-pronouns", id: "ebrw-rhetorical-synthesis",
title: "Agreement: Pronouns & Confused Words", title: "Rhetorical Synthesis",
description: description:
"Pronoun-antecedent agreement, who vs. whom, and frequently confused pairs: its/it's, their/there/they're, whose/who's.", "Combine information from notes or sources to support a claim or improve the effectiveness of writing.",
iconName: "BookOpen", iconName: "TrendingUp",
color: "purple", color: "rose",
category: "Standard English Conventions", category: "Expression of Ideas",
},
{
id: "ebrw-verbs",
title: "Parts of Speech: Verbs & Verb Forms",
description:
"Tense consistency, past perfect, present perfect, finite vs. nonfinite verbs, irregular forms, and subjunctive mood.",
iconName: "BookOpen",
color: "purple",
category: "Standard English Conventions",
},
{
id: "ebrw-sentence-structure",
title: "Sentence Structure: Parallelism & Modifiers",
description:
"Parallel construction in series and comparisons, and fixing misplaced or dangling modifiers — two of the SAT's top traps.",
iconName: "Layers",
color: "purple",
category: "Standard English Conventions",
}, },
]; ];