feat(lessons): add lessons from client db
This commit is contained in:
113
src/components/lessons/ParallelPerpendicularWidget.tsx
Normal file
113
src/components/lessons/ParallelPerpendicularWidget.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const ParallelPerpendicularWidget: React.FC = () => {
|
||||
const [slope, setSlope] = useState(2);
|
||||
const [showParallel, setShowParallel] = useState(true);
|
||||
const [showPerpendicular, setShowPerpendicular] = useState(true);
|
||||
|
||||
const range = 10;
|
||||
const scale = 20; // 20px per unit
|
||||
const size = 300;
|
||||
const center = size / 2;
|
||||
|
||||
const toPx = (v: number, isY = false) => isY ? center - v * scale : center + v * scale;
|
||||
|
||||
const getLinePath = (m: number, b: number) => {
|
||||
// Find two points on edges of view box (-range, +range)
|
||||
// y = mx + b
|
||||
// Need to clip lines to viewBox to be nice
|
||||
const x1 = -range;
|
||||
const y1 = m * x1 + b;
|
||||
const x2 = range;
|
||||
const y2 = m * x2 + b;
|
||||
return `M ${toPx(x1)} ${toPx(y1, true)} L ${toPx(x2)} ${toPx(y2, true)}`;
|
||||
};
|
||||
|
||||
const perpSlope = slope === 0 ? 1000 : -1 / slope; // Hack for vertical
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
<div className="w-full md:w-1/3 space-y-6">
|
||||
<div className="p-4 bg-slate-50 border border-slate-200 rounded-xl">
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Reference Slope (m)</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range" min="-4" max="4" step="0.5"
|
||||
value={slope} onChange={e => setSlope(parseFloat(e.target.value))}
|
||||
className="flex-1 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600"
|
||||
/>
|
||||
<span className="font-mono font-bold text-indigo-700 w-12 text-right">{slope}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={() => setShowParallel(!showParallel)}
|
||||
className={`w-full flex items-center justify-between p-3 rounded-lg border-2 transition-all ${
|
||||
showParallel ? 'border-sky-500 bg-sky-50 text-sky-900' : 'border-slate-200 text-slate-400'
|
||||
}`}
|
||||
>
|
||||
<span className="font-bold">Parallel</span>
|
||||
<span className="font-mono text-sm">{showParallel ? `m = ${slope}` : 'Hidden'}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowPerpendicular(!showPerpendicular)}
|
||||
className={`w-full flex items-center justify-between p-3 rounded-lg border-2 transition-all ${
|
||||
showPerpendicular ? 'border-rose-500 bg-rose-50 text-rose-900' : 'border-slate-200 text-slate-400'
|
||||
}`}
|
||||
>
|
||||
<span className="font-bold">Perpendicular</span>
|
||||
<span className="font-mono text-sm">{showPerpendicular ? `m = ${slope === 0 ? 'Undef' : (-1/slope).toFixed(2)}` : 'Hidden'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-slate-500 bg-slate-50 p-3 rounded">
|
||||
<strong>Key Rule:</strong> Perpendicular slopes are negative reciprocals ($m$ vs $-1/m$). Their product is always -1.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex justify-center">
|
||||
<div className="relative w-[300px] h-[300px] border border-slate-200 rounded-xl overflow-hidden bg-white">
|
||||
<svg width="300" height="300" viewBox="0 0 300 300">
|
||||
<defs>
|
||||
<pattern id="grid-p" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#f1f5f9" strokeWidth="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid-p)" />
|
||||
|
||||
{/* Axes */}
|
||||
<line x1="0" y1={center} x2={size} y2={center} stroke="#cbd5e1" strokeWidth="2" />
|
||||
<line x1={center} y1="0" x2={center} y2={size} stroke="#cbd5e1" strokeWidth="2" />
|
||||
|
||||
{/* Reference Line (Indigo) */}
|
||||
<path d={getLinePath(slope, 0)} stroke="#4f46e5" strokeWidth="3" />
|
||||
|
||||
{/* Parallel Line (Sky) - Shifted up by 3 units */}
|
||||
{showParallel && (
|
||||
<path d={getLinePath(slope, 3)} stroke="#0ea5e9" strokeWidth="3" strokeDasharray="5,5" />
|
||||
)}
|
||||
|
||||
{/* Perpendicular Line (Rose) - Through Origin */}
|
||||
{showPerpendicular && (
|
||||
<>
|
||||
<path d={getLinePath(perpSlope, 0)} stroke="#e11d48" strokeWidth="3" />
|
||||
{/* Right Angle Marker approx */}
|
||||
<rect
|
||||
x={center} y={center} width="15" height="15"
|
||||
fill="rgba(225, 29, 72, 0.2)"
|
||||
transform={`rotate(${-Math.atan(slope) * 180 / Math.PI} ${center} ${center})`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParallelPerpendicularWidget;
|
||||
Reference in New Issue
Block a user