chore(build): refactor codebase for production
This commit is contained in:
@ -1,32 +1,35 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import React, { useState, useRef } from "react";
|
||||
|
||||
type Mode = 'AA' | 'SAS' | 'SSS';
|
||||
type Mode = "AA" | "SAS" | "SSS";
|
||||
|
||||
const SimilarityTestsWidget: React.FC = () => {
|
||||
const [mode, setMode] = useState<Mode>('AA');
|
||||
const [mode, setMode] = useState<Mode>("AA");
|
||||
const [scale, setScale] = useState(1.5);
|
||||
// Store Vertex B's position relative to A (x offset, y height)
|
||||
// A is at (40, 220). SVG Y is down.
|
||||
const [vertexB, setVertexB] = useState({ x: 40, y: 100 });
|
||||
const [vertexB, setVertexB] = useState({ x: 40, y: 100 });
|
||||
const isDragging = useRef(false);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
// Triangle 1 (ABC) - Fixed base AC
|
||||
const A = { x: 40, y: 220 };
|
||||
const C = { x: 120, y: 220 }; // Base length = 80
|
||||
|
||||
|
||||
// Calculate B in SVG coordinates based on state
|
||||
// vertexB.y is the height (upwards), so we subtract from A.y
|
||||
const B = { x: A.x + vertexB.x, y: A.y - vertexB.y };
|
||||
|
||||
// Calculate lengths and angles for T1
|
||||
const dist = (p1: {x:number, y:number}, p2: {x:number, y:number}) => Math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2);
|
||||
const dist = (p1: { x: number; y: number }, p2: { x: number; y: number }) =>
|
||||
Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
|
||||
const c1 = dist(A, B); // side c (opp C) - Side AB
|
||||
const a1 = dist(B, C); // side a (opp A) - Side BC
|
||||
const b1 = dist(A, C); // side b (opp B) - Side AC (Base)
|
||||
|
||||
const getAngle = (a: number, b: number, c: number) => {
|
||||
return Math.acos((b**2 + c**2 - a**2) / (2 * b * c)) * (180 / Math.PI);
|
||||
return (
|
||||
Math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) * (180 / Math.PI)
|
||||
);
|
||||
};
|
||||
|
||||
const angleA = getAngle(a1, b1, c1);
|
||||
@ -34,18 +37,18 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
// const angleC = getAngle(c1, a1, b1);
|
||||
|
||||
// Triangle 2 (DEF) - Scaled version of ABC
|
||||
// Start D with enough margin. Max width of T1 is ~100-140.
|
||||
// Start D with enough margin. Max width of T1 is ~100-140.
|
||||
// Let's place D at x=240.
|
||||
const D = { x: 240, y: 220 };
|
||||
|
||||
|
||||
// F is horizontal from D by scaled base length
|
||||
const F = { x: D.x + b1 * scale, y: D.y };
|
||||
|
||||
|
||||
// E is scaled vector AB from D
|
||||
const vecAB = { x: B.x - A.x, y: B.y - A.y };
|
||||
const E = {
|
||||
x: D.x + vecAB.x * scale,
|
||||
y: D.y + vecAB.y * scale
|
||||
const E = {
|
||||
x: D.x + vecAB.x * scale,
|
||||
y: D.y + vecAB.y * scale,
|
||||
};
|
||||
|
||||
// Interaction
|
||||
@ -54,17 +57,17 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
const rect = svgRef.current.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
|
||||
// Constraints for B relative to A
|
||||
// Keep B within reasonable bounds to prevent breaking the layout
|
||||
// Base is 40 to 120. B.x can range from 0 to 140?
|
||||
const newX = x - A.x;
|
||||
const height = A.y - y;
|
||||
|
||||
|
||||
// Clamp
|
||||
const clampedX = Math.max(-20, Math.min(100, newX));
|
||||
const clampedH = Math.max(40, Math.min(180, height));
|
||||
|
||||
|
||||
setVertexB({ x: clampedX, y: clampedH });
|
||||
};
|
||||
|
||||
@ -72,18 +75,22 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
const sideColor = "#059669"; // Emerald
|
||||
|
||||
// Helper: draw filled angle wedge + labelled badge at a vertex
|
||||
const angleC = 180 - angleA - angleB;
|
||||
const renderAngle = (
|
||||
vx: number, vy: number,
|
||||
p1x: number, p1y: number,
|
||||
p2x: number, p2y: number,
|
||||
vx: number,
|
||||
vy: number,
|
||||
p1x: number,
|
||||
p1y: number,
|
||||
p2x: number,
|
||||
p2y: number,
|
||||
deg: number,
|
||||
r = 28
|
||||
r = 28,
|
||||
) => {
|
||||
const d1 = Math.atan2(p1y - vy, p1x - vx);
|
||||
const d2 = Math.atan2(p2y - vy, p2x - vx);
|
||||
const sx = vx + r * Math.cos(d1), sy = vy + r * Math.sin(d1);
|
||||
const ex = vx + r * Math.cos(d2), ey = vy + r * Math.sin(d2);
|
||||
const sx = vx + r * Math.cos(d1),
|
||||
sy = vy + r * Math.sin(d1);
|
||||
const ex = vx + r * Math.cos(d2),
|
||||
ey = vy + r * Math.sin(d2);
|
||||
const cross = (p1x - vx) * (p2y - vy) - (p1y - vy) * (p2x - vx);
|
||||
const sweep = cross > 0 ? 1 : 0;
|
||||
let diff = d2 - d1;
|
||||
@ -91,13 +98,40 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
while (diff < -Math.PI) diff += 2 * Math.PI;
|
||||
const mid = d1 + diff / 2;
|
||||
const lr = r + 18;
|
||||
const lx = vx + lr * Math.cos(mid), ly = vy + lr * Math.sin(mid);
|
||||
const lx = vx + lr * Math.cos(mid),
|
||||
ly = vy + lr * Math.sin(mid);
|
||||
const txt = `${Math.round(deg)}°`;
|
||||
return (
|
||||
<g>
|
||||
<path d={`M ${vx} ${vy} L ${sx} ${sy} A ${r} ${r} 0 0 ${sweep} ${ex} ${ey} Z`} fill={angleColor} fillOpacity={0.12} stroke={angleColor} strokeWidth={2} />
|
||||
<rect x={lx - 18} y={ly - 10} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={angleColor} strokeWidth={0.8} />
|
||||
<text x={lx} y={ly + 5} textAnchor="middle" fill={angleColor} fontSize="13" fontWeight="bold" fontFamily="system-ui">{txt}</text>
|
||||
<path
|
||||
d={`M ${vx} ${vy} L ${sx} ${sy} A ${r} ${r} 0 0 ${sweep} ${ex} ${ey} Z`}
|
||||
fill={angleColor}
|
||||
fillOpacity={0.12}
|
||||
stroke={angleColor}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<rect
|
||||
x={lx - 18}
|
||||
y={ly - 10}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={angleColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={lx}
|
||||
y={ly + 5}
|
||||
textAnchor="middle"
|
||||
fill={angleColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
fontFamily="system-ui"
|
||||
>
|
||||
{txt}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
@ -106,14 +140,14 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200">
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-between items-center mb-6">
|
||||
<div className="flex bg-slate-100 p-1 rounded-lg overflow-x-auto max-w-full">
|
||||
{(['AA', 'SAS', 'SSS'] as Mode[]).map(m => (
|
||||
{(["AA", "SAS", "SSS"] as Mode[]).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m)}
|
||||
className={`px-4 py-2 rounded-md text-sm font-bold transition-all whitespace-nowrap ${
|
||||
mode === m
|
||||
? 'bg-white text-rose-600 shadow-sm'
|
||||
: 'text-slate-500 hover:text-rose-600'
|
||||
? "bg-white text-rose-600 shadow-sm"
|
||||
: "text-slate-500 hover:text-rose-600"
|
||||
}`}
|
||||
>
|
||||
{m}
|
||||
@ -122,179 +156,490 @@ const SimilarityTestsWidget: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 bg-slate-50 px-4 py-2 rounded-lg border border-slate-200">
|
||||
<span className="text-xs font-bold text-slate-400 uppercase">Scale (k)</span>
|
||||
<input
|
||||
type="range" min="0.5" max="2.5" step="0.1"
|
||||
value={scale}
|
||||
onChange={e => setScale(parseFloat(e.target.value))}
|
||||
className="w-24 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-rose-600"
|
||||
/>
|
||||
<span className="font-mono font-bold text-rose-600 text-sm w-12 text-right">{scale.toFixed(1)}x</span>
|
||||
<span className="text-xs font-bold text-slate-400 uppercase">
|
||||
Scale (k)
|
||||
</span>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max="2.5"
|
||||
step="0.1"
|
||||
value={scale}
|
||||
onChange={(e) => setScale(parseFloat(e.target.value))}
|
||||
className="w-24 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-rose-600"
|
||||
/>
|
||||
<span className="font-mono font-bold text-rose-600 text-sm w-12 text-right">
|
||||
{scale.toFixed(1)}x
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative border border-slate-100 rounded-lg bg-slate-50 mb-6 overflow-hidden flex justify-center">
|
||||
<svg
|
||||
ref={svgRef}
|
||||
width="550" height="280"
|
||||
className="cursor-default select-none"
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={() => isDragging.current = false}
|
||||
onMouseLeave={() => isDragging.current = false}
|
||||
ref={svgRef}
|
||||
width="550"
|
||||
height="280"
|
||||
className="cursor-default select-none"
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={() => (isDragging.current = false)}
|
||||
onMouseLeave={() => (isDragging.current = false)}
|
||||
>
|
||||
<defs>
|
||||
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#e2e8f0" strokeWidth="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
<defs>
|
||||
<pattern
|
||||
id="grid"
|
||||
width="20"
|
||||
height="20"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
d="M 20 0 L 0 0 0 20"
|
||||
fill="none"
|
||||
stroke="#e2e8f0"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
|
||||
{/* Triangle 1 (ABC) */}
|
||||
<path d={`M ${A.x} ${A.y} L ${B.x} ${B.y} L ${C.x} ${C.y} Z`} fill="rgba(255, 255, 255, 0.8)" stroke="#334155" strokeWidth="2" />
|
||||
{/* Triangle 1 (ABC) */}
|
||||
<path
|
||||
d={`M ${A.x} ${A.y} L ${B.x} ${B.y} L ${C.x} ${C.y} Z`}
|
||||
fill="rgba(255, 255, 255, 0.8)"
|
||||
stroke="#334155"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
|
||||
{/* Vertices T1 */}
|
||||
<circle cx={A.x} cy={A.y} r="4" fill="#334155" />
|
||||
<text x={A.x - 16} y={A.y + 14} fontWeight="bold" fill="#334155" fontSize="14">A</text>
|
||||
<circle cx={C.x} cy={C.y} r="4" fill="#334155" />
|
||||
<text x={C.x + 8} y={C.y + 14} fontWeight="bold" fill="#334155" fontSize="14">C</text>
|
||||
{/* Vertices T1 */}
|
||||
<circle cx={A.x} cy={A.y} r="4" fill="#334155" />
|
||||
<text
|
||||
x={A.x - 16}
|
||||
y={A.y + 14}
|
||||
fontWeight="bold"
|
||||
fill="#334155"
|
||||
fontSize="14"
|
||||
>
|
||||
A
|
||||
</text>
|
||||
<circle cx={C.x} cy={C.y} r="4" fill="#334155" />
|
||||
<text
|
||||
x={C.x + 8}
|
||||
y={C.y + 14}
|
||||
fontWeight="bold"
|
||||
fill="#334155"
|
||||
fontSize="14"
|
||||
>
|
||||
C
|
||||
</text>
|
||||
|
||||
{/* Draggable B */}
|
||||
<g onMouseDown={() => isDragging.current = true} className="cursor-grab active:cursor-grabbing">
|
||||
<circle cx={B.x} cy={B.y} r="20" fill="transparent" /> {/* Hit area */}
|
||||
<circle cx={B.x} cy={B.y} r="7" fill="#f43f5e" stroke="white" strokeWidth="2" />
|
||||
<text x={B.x} y={B.y - 16} textAnchor="middle" fontWeight="bold" fill="#f43f5e" fontSize="14">B</text>
|
||||
</g>
|
||||
{/* Draggable B */}
|
||||
<g
|
||||
onMouseDown={() => (isDragging.current = true)}
|
||||
className="cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
<circle cx={B.x} cy={B.y} r="20" fill="transparent" />{" "}
|
||||
{/* Hit area */}
|
||||
<circle
|
||||
cx={B.x}
|
||||
cy={B.y}
|
||||
r="7"
|
||||
fill="#f43f5e"
|
||||
stroke="white"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<text
|
||||
x={B.x}
|
||||
y={B.y - 16}
|
||||
textAnchor="middle"
|
||||
fontWeight="bold"
|
||||
fill="#f43f5e"
|
||||
fontSize="14"
|
||||
>
|
||||
B
|
||||
</text>
|
||||
</g>
|
||||
|
||||
{/* Triangle 2 (DEF) */}
|
||||
<path d={`M ${D.x} ${D.y} L ${E.x} ${E.y} L ${F.x} ${F.y} Z`} fill="rgba(255, 255, 255, 0.8)" stroke="#334155" strokeWidth="2" />
|
||||
{/* Triangle 2 (DEF) */}
|
||||
<path
|
||||
d={`M ${D.x} ${D.y} L ${E.x} ${E.y} L ${F.x} ${F.y} Z`}
|
||||
fill="rgba(255, 255, 255, 0.8)"
|
||||
stroke="#334155"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
|
||||
<circle cx={D.x} cy={D.y} r="4" fill="#334155" />
|
||||
<text x={D.x - 16} y={D.y + 14} fontWeight="bold" fill="#334155" fontSize="14">D</text>
|
||||
<circle cx={F.x} cy={F.y} r="4" fill="#334155" />
|
||||
<text x={F.x + 8} y={F.y + 14} fontWeight="bold" fill="#334155" fontSize="14">F</text>
|
||||
<circle cx={E.x} cy={E.y} r="4" fill="#334155" />
|
||||
<text x={E.x} y={E.y - 16} textAnchor="middle" fontWeight="bold" fill="#334155" fontSize="14">E</text>
|
||||
<circle cx={D.x} cy={D.y} r="4" fill="#334155" />
|
||||
<text
|
||||
x={D.x - 16}
|
||||
y={D.y + 14}
|
||||
fontWeight="bold"
|
||||
fill="#334155"
|
||||
fontSize="14"
|
||||
>
|
||||
D
|
||||
</text>
|
||||
<circle cx={F.x} cy={F.y} r="4" fill="#334155" />
|
||||
<text
|
||||
x={F.x + 8}
|
||||
y={F.y + 14}
|
||||
fontWeight="bold"
|
||||
fill="#334155"
|
||||
fontSize="14"
|
||||
>
|
||||
F
|
||||
</text>
|
||||
<circle cx={E.x} cy={E.y} r="4" fill="#334155" />
|
||||
<text
|
||||
x={E.x}
|
||||
y={E.y - 16}
|
||||
textAnchor="middle"
|
||||
fontWeight="bold"
|
||||
fill="#334155"
|
||||
fontSize="14"
|
||||
>
|
||||
E
|
||||
</text>
|
||||
|
||||
{/* Visual Overlays based on Mode */}
|
||||
{mode === 'AA' && (
|
||||
<>
|
||||
{/* Angle A and D (base-left) */}
|
||||
{renderAngle(A.x, A.y, C.x, C.y, B.x, B.y, angleA)}
|
||||
{renderAngle(D.x, D.y, F.x, F.y, E.x, E.y, angleA)}
|
||||
{/* Angle B and E (apex) */}
|
||||
{renderAngle(B.x, B.y, A.x, A.y, C.x, C.y, angleB)}
|
||||
{renderAngle(E.x, E.y, D.x, D.y, F.x, F.y, angleB)}
|
||||
</>
|
||||
)}
|
||||
{/* Visual Overlays based on Mode */}
|
||||
{mode === "AA" && (
|
||||
<>
|
||||
{/* Angle A and D (base-left) */}
|
||||
{renderAngle(A.x, A.y, C.x, C.y, B.x, B.y, angleA)}
|
||||
{renderAngle(D.x, D.y, F.x, F.y, E.x, E.y, angleA)}
|
||||
{/* Angle B and E (apex) */}
|
||||
{renderAngle(B.x, B.y, A.x, A.y, C.x, C.y, angleB)}
|
||||
{renderAngle(E.x, E.y, D.x, D.y, F.x, F.y, angleB)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{mode === 'SAS' && (
|
||||
<>
|
||||
{/* Included Angle A and D */}
|
||||
{renderAngle(A.x, A.y, C.x, C.y, B.x, B.y, angleA)}
|
||||
{renderAngle(D.x, D.y, F.x, F.y, E.x, E.y, angleA)}
|
||||
{mode === "SAS" && (
|
||||
<>
|
||||
{/* Included Angle A and D */}
|
||||
{renderAngle(A.x, A.y, C.x, C.y, B.x, B.y, angleA)}
|
||||
{renderAngle(D.x, D.y, F.x, F.y, E.x, E.y, angleA)}
|
||||
|
||||
{/* Side labels with background badges */}
|
||||
{/* Side AB / DE */}
|
||||
<rect x={(A.x + B.x)/2 - 24} y={(A.y + B.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(A.x + B.x)/2 - 6} y={(A.y + B.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(c1)}</text>
|
||||
<rect x={(D.x + E.x)/2 - 24} y={(D.y + E.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(D.x + E.x)/2 - 6} y={(D.y + E.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(c1 * scale)}</text>
|
||||
{/* Side labels with background badges */}
|
||||
{/* Side AB / DE */}
|
||||
<rect
|
||||
x={(A.x + B.x) / 2 - 24}
|
||||
y={(A.y + B.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(A.x + B.x) / 2 - 6}
|
||||
y={(A.y + B.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(c1)}
|
||||
</text>
|
||||
<rect
|
||||
x={(D.x + E.x) / 2 - 24}
|
||||
y={(D.y + E.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(D.x + E.x) / 2 - 6}
|
||||
y={(D.y + E.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(c1 * scale)}
|
||||
</text>
|
||||
|
||||
{/* Side AC / DF */}
|
||||
<rect x={(A.x + C.x)/2 - 18} y={A.y + 4} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(A.x + C.x)/2} y={A.y + 18} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(b1)}</text>
|
||||
<rect x={(D.x + F.x)/2 - 18} y={D.y + 4} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(D.x + F.x)/2} y={D.y + 18} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(b1 * scale)}</text>
|
||||
</>
|
||||
)}
|
||||
{/* Side AC / DF */}
|
||||
<rect
|
||||
x={(A.x + C.x) / 2 - 18}
|
||||
y={A.y + 4}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(A.x + C.x) / 2}
|
||||
y={A.y + 18}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(b1)}
|
||||
</text>
|
||||
<rect
|
||||
x={(D.x + F.x) / 2 - 18}
|
||||
y={D.y + 4}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(D.x + F.x) / 2}
|
||||
y={D.y + 18}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(b1 * scale)}
|
||||
</text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{mode === 'SSS' && (
|
||||
<>
|
||||
{/* Side AB / DE */}
|
||||
<rect x={(A.x + B.x)/2 - 24} y={(A.y + B.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(A.x + B.x)/2 - 6} y={(A.y + B.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(c1)}</text>
|
||||
<rect x={(D.x + E.x)/2 - 24} y={(D.y + E.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(D.x + E.x)/2 - 6} y={(D.y + E.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(c1 * scale)}</text>
|
||||
{mode === "SSS" && (
|
||||
<>
|
||||
{/* Side AB / DE */}
|
||||
<rect
|
||||
x={(A.x + B.x) / 2 - 24}
|
||||
y={(A.y + B.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(A.x + B.x) / 2 - 6}
|
||||
y={(A.y + B.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(c1)}
|
||||
</text>
|
||||
<rect
|
||||
x={(D.x + E.x) / 2 - 24}
|
||||
y={(D.y + E.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(D.x + E.x) / 2 - 6}
|
||||
y={(D.y + E.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(c1 * scale)}
|
||||
</text>
|
||||
|
||||
{/* Side AC / DF */}
|
||||
<rect x={(A.x + C.x)/2 - 18} y={A.y + 4} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(A.x + C.x)/2} y={A.y + 18} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(b1)}</text>
|
||||
<rect x={(D.x + F.x)/2 - 18} y={D.y + 4} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(D.x + F.x)/2} y={D.y + 18} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(b1 * scale)}</text>
|
||||
{/* Side AC / DF */}
|
||||
<rect
|
||||
x={(A.x + C.x) / 2 - 18}
|
||||
y={A.y + 4}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(A.x + C.x) / 2}
|
||||
y={A.y + 18}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(b1)}
|
||||
</text>
|
||||
<rect
|
||||
x={(D.x + F.x) / 2 - 18}
|
||||
y={D.y + 4}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(D.x + F.x) / 2}
|
||||
y={D.y + 18}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(b1 * scale)}
|
||||
</text>
|
||||
|
||||
{/* Side BC / EF */}
|
||||
<rect x={(B.x + C.x)/2 + 2} y={(B.y + C.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(B.x + C.x)/2 + 20} y={(B.y + C.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(a1)}</text>
|
||||
<rect x={(E.x + F.x)/2 + 2} y={(E.y + F.y)/2 - 12} width={36} height={20} rx={5} fill="white" fillOpacity={0.92} stroke={sideColor} strokeWidth={0.8} />
|
||||
<text x={(E.x + F.x)/2 + 20} y={(E.y + F.y)/2 + 3} fill={sideColor} fontSize="13" fontWeight="bold" textAnchor="middle">{Math.round(a1 * scale)}</text>
|
||||
</>
|
||||
)}
|
||||
{/* Side BC / EF */}
|
||||
<rect
|
||||
x={(B.x + C.x) / 2 + 2}
|
||||
y={(B.y + C.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(B.x + C.x) / 2 + 20}
|
||||
y={(B.y + C.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(a1)}
|
||||
</text>
|
||||
<rect
|
||||
x={(E.x + F.x) / 2 + 2}
|
||||
y={(E.y + F.y) / 2 - 12}
|
||||
width={36}
|
||||
height={20}
|
||||
rx={5}
|
||||
fill="white"
|
||||
fillOpacity={0.92}
|
||||
stroke={sideColor}
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<text
|
||||
x={(E.x + F.x) / 2 + 20}
|
||||
y={(E.y + F.y) / 2 + 3}
|
||||
fill={sideColor}
|
||||
fontSize="13"
|
||||
fontWeight="bold"
|
||||
textAnchor="middle"
|
||||
>
|
||||
{Math.round(a1 * scale)}
|
||||
</text>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="bg-rose-50 border border-rose-100 rounded-lg p-4 text-rose-900">
|
||||
<h4 className="font-bold mb-2 flex items-center gap-2 text-lg">
|
||||
<span className="w-3 h-3 rounded-full bg-rose-500"></span>
|
||||
{mode === 'AA' && "Angle-Angle (AA) Similarity"}
|
||||
{mode === 'SAS' && "Side-Angle-Side (SAS) Similarity"}
|
||||
{mode === 'SSS' && "Side-Side-Side (SSS) Similarity"}
|
||||
</h4>
|
||||
<div className="text-sm font-mono space-y-2">
|
||||
{mode === 'AA' && (
|
||||
<>
|
||||
<p className="leading-relaxed">If two angles of one triangle are equal to two angles of another triangle, then the triangles are similar.</p>
|
||||
<div className="flex gap-8 mt-2">
|
||||
<div>
|
||||
<span className="text-xs font-bold text-rose-400 uppercase">First Angle</span>
|
||||
<p className="font-bold text-lg">∠A = ∠D = {Math.round(angleA)}°</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-rose-400 uppercase">Second Angle</span>
|
||||
<p className="font-bold text-lg">∠B = ∠E = {Math.round(angleB)}°</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{mode === 'SAS' && (
|
||||
<>
|
||||
<p className="leading-relaxed">If two sides are proportional and the included angles are equal, the triangles are similar.</p>
|
||||
<div className="grid grid-cols-2 gap-4 mt-2">
|
||||
<div className="bg-white p-2 rounded border border-rose-100">
|
||||
<p className="text-xs text-rose-500 font-bold uppercase">Side Ratio (c)</p>
|
||||
<p>DE / AB = {(c1*scale).toFixed(0)} / {c1.toFixed(0)} = <strong>{scale.toFixed(1)}</strong></p>
|
||||
</div>
|
||||
<div className="bg-white p-2 rounded border border-rose-100">
|
||||
<p className="text-xs text-rose-500 font-bold uppercase">Side Ratio (b)</p>
|
||||
<p>DF / AC = {(b1*scale).toFixed(0)} / {b1.toFixed(0)} = <strong>{scale.toFixed(1)}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 font-bold text-rose-800">Included Angle: ∠A = ∠D = {Math.round(angleA)}°</p>
|
||||
</>
|
||||
)}
|
||||
{mode === 'SSS' && (
|
||||
<>
|
||||
<p className="leading-relaxed">If the corresponding sides of two triangles are proportional, then the triangles are similar.</p>
|
||||
<p className="bg-white inline-block px-2 py-1 rounded border border-rose-100 font-bold text-rose-600 mb-2">Scale Factor k = {scale.toFixed(1)}</p>
|
||||
<div className="grid grid-cols-3 gap-2 text-center text-xs">
|
||||
<div className="bg-white p-1 rounded">
|
||||
DE/AB = {scale.toFixed(1)}
|
||||
</div>
|
||||
<div className="bg-white p-1 rounded">
|
||||
EF/BC = {scale.toFixed(1)}
|
||||
</div>
|
||||
<div className="bg-white p-1 rounded">
|
||||
DF/AC = {scale.toFixed(1)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-rose-400 mt-4 border-t border-rose-100 pt-2">
|
||||
Drag vertex <strong>B</strong> on the first triangle to explore different shapes!
|
||||
</p>
|
||||
<h4 className="font-bold mb-2 flex items-center gap-2 text-lg">
|
||||
<span className="w-3 h-3 rounded-full bg-rose-500"></span>
|
||||
{mode === "AA" && "Angle-Angle (AA) Similarity"}
|
||||
{mode === "SAS" && "Side-Angle-Side (SAS) Similarity"}
|
||||
{mode === "SSS" && "Side-Side-Side (SSS) Similarity"}
|
||||
</h4>
|
||||
<div className="text-sm font-mono space-y-2">
|
||||
{mode === "AA" && (
|
||||
<>
|
||||
<p className="leading-relaxed">
|
||||
If two angles of one triangle are equal to two angles of another
|
||||
triangle, then the triangles are similar.
|
||||
</p>
|
||||
<div className="flex gap-8 mt-2">
|
||||
<div>
|
||||
<span className="text-xs font-bold text-rose-400 uppercase">
|
||||
First Angle
|
||||
</span>
|
||||
<p className="font-bold text-lg">
|
||||
∠A = ∠D = {Math.round(angleA)}°
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-rose-400 uppercase">
|
||||
Second Angle
|
||||
</span>
|
||||
<p className="font-bold text-lg">
|
||||
∠B = ∠E = {Math.round(angleB)}°
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{mode === "SAS" && (
|
||||
<>
|
||||
<p className="leading-relaxed">
|
||||
If two sides are proportional and the included angles are equal,
|
||||
the triangles are similar.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4 mt-2">
|
||||
<div className="bg-white p-2 rounded border border-rose-100">
|
||||
<p className="text-xs text-rose-500 font-bold uppercase">
|
||||
Side Ratio (c)
|
||||
</p>
|
||||
<p>
|
||||
DE / AB = {(c1 * scale).toFixed(0)} / {c1.toFixed(0)} ={" "}
|
||||
<strong>{scale.toFixed(1)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-2 rounded border border-rose-100">
|
||||
<p className="text-xs text-rose-500 font-bold uppercase">
|
||||
Side Ratio (b)
|
||||
</p>
|
||||
<p>
|
||||
DF / AC = {(b1 * scale).toFixed(0)} / {b1.toFixed(0)} ={" "}
|
||||
<strong>{scale.toFixed(1)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 font-bold text-rose-800">
|
||||
Included Angle: ∠A = ∠D = {Math.round(angleA)}°
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{mode === "SSS" && (
|
||||
<>
|
||||
<p className="leading-relaxed">
|
||||
If the corresponding sides of two triangles are proportional,
|
||||
then the triangles are similar.
|
||||
</p>
|
||||
<p className="bg-white inline-block px-2 py-1 rounded border border-rose-100 font-bold text-rose-600 mb-2">
|
||||
Scale Factor k = {scale.toFixed(1)}
|
||||
</p>
|
||||
<div className="grid grid-cols-3 gap-2 text-center text-xs">
|
||||
<div className="bg-white p-1 rounded">
|
||||
DE/AB = {scale.toFixed(1)}
|
||||
</div>
|
||||
<div className="bg-white p-1 rounded">
|
||||
EF/BC = {scale.toFixed(1)}
|
||||
</div>
|
||||
<div className="bg-white p-1 rounded">
|
||||
DF/AC = {scale.toFixed(1)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-rose-400 mt-4 border-t border-rose-100 pt-2">
|
||||
Drag vertex <strong>B</strong> on the first triangle to explore
|
||||
different shapes!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimilarityTestsWidget;
|
||||
export default SimilarityTestsWidget;
|
||||
|
||||
Reference in New Issue
Block a user