Files
edbridge-scholars/src/components/lessons/CompositeSolidsWidget.tsx
2026-03-01 20:24:14 +06:00

256 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from "react";
const CompositeSolidsWidget: React.FC = () => {
const [isMerged, setIsMerged] = useState(false);
const [w, setW] = useState(60);
const [h, setH] = useState(80);
const [d, setD] = useState(60);
// Surface Area Calcs
const singleSA = 2 * (w * h + w * d + h * d);
const hiddenFaceArea = d * h;
const totalSeparateSA = singleSA * 2;
const mergedSA = totalSeparateSA - 2 * hiddenFaceArea;
// Helper to generate a face style
const getFaceStyle = (
width: number,
height: number,
transform: string,
color: string,
) => ({
width: `${width}px`,
height: `${height}px`,
position: "absolute" as const,
left: "50%",
top: "50%",
marginLeft: `-${width / 2}px`,
marginTop: `-${height / 2}px`,
transform: transform,
backgroundColor: color,
border: "1px solid rgba(255,255,255,0.3)",
display: "flex",
alignItems: "center",
justifyContent: "center",
backfaceVisibility: "hidden" as const, // Hide backfaces for cleaner look if opaque
transition: "all 0.5s",
});
// Prism Component
const Prism = ({
positionX,
baseHue,
highlightSide, // 'left' or 'right' indicates the face to highlight red
}: {
positionX: number;
baseHue: "indigo" | "sky";
highlightSide?: "left" | "right";
}) => {
// Define shades based on hue
// Lighting: Top is lightest, Front is base, Side is darkest
const colors =
baseHue === "indigo"
? { top: "#818cf8", front: "#6366f1", side: "#4f46e5" } // Indigo 400, 500, 600
: { top: "#38bdf8", front: "#0ea5e9", side: "#0284c7" }; // Sky 400, 500, 600
const hiddenColor = "#f43f5e"; // Rose 500
return (
<div
className="absolute top-0 left-0 transition-all duration-700 ease-in-out transform-style-3d"
style={{ transform: `translateX(${positionX}px)` }}
>
{/* Front (w x h) */}
<div
style={getFaceStyle(w, h, `translateZ(${d / 2}px)`, colors.front)}
/>
{/* Back (w x h) - usually hidden but good for completeness */}
<div
style={getFaceStyle(
w,
h,
`rotateY(180deg) translateZ(${d / 2}px)`,
colors.front,
)}
/>
{/* Right (d x h) */}
<div
style={getFaceStyle(
d,
h,
`rotateY(90deg) translateZ(${w / 2}px)`,
highlightSide === "right" ? hiddenColor : colors.side,
)}
>
{highlightSide === "right" && (
<span className="text-white font-bold text-xs rotate-90 tracking-widest">
FACE
</span>
)}
</div>
{/* Left (d x h) */}
<div
style={getFaceStyle(
d,
h,
`rotateY(-90deg) translateZ(${w / 2}px)`,
highlightSide === "left" ? hiddenColor : colors.side,
)}
>
{highlightSide === "left" && (
<span className="text-white font-bold text-xs -rotate-90 tracking-widest">
FACE
</span>
)}
</div>
{/* Top (w x d) */}
<div
style={getFaceStyle(
w,
d,
`rotateX(90deg) translateZ(${h / 2}px)`,
colors.top,
)}
/>
{/* Bottom (w x d) */}
<div
style={getFaceStyle(
w,
d,
`rotateX(-90deg) translateZ(${h / 2}px)`,
colors.side,
)}
/>
</div>
);
};
// Gap Logic
const gap = isMerged ? 0 : 40;
const posLeft = -(w / 2 + gap / 2);
const posRight = w / 2 + gap / 2;
return (
<div className="bg-white p-6 rounded-xl shadow-lg border border-slate-200 flex flex-col items-center">
<div className="flex justify-between w-full items-center mb-8">
<h3 className="text-lg font-bold text-slate-800">
The "Hidden Face" Trap
</h3>
<button
onClick={() => setIsMerged(!isMerged)}
className={`px-6 py-2 rounded-full font-bold shadow-sm transition-all text-sm ${isMerged ? "bg-slate-200 text-slate-700 hover:bg-slate-300" : "bg-indigo-600 text-white hover:bg-indigo-700"}`}
>
{isMerged ? "Separate Prisms" : "Glue Together"}
</button>
</div>
{/* 3D Scene */}
<div className="relative h-72 w-full flex items-center justify-center perspective-1000 overflow-visible mb-8">
{/* Container rotated for Isometric-ish view */}
<div
className="relative transform-style-3d transition-transform duration-700"
style={{ transform: "rotateX(-15deg) rotateY(35deg)" }}
>
{/* Left Prism (Indigo) - Right face hidden */}
<Prism positionX={posLeft} baseHue="indigo" highlightSide="right" />
{/* Right Prism (Sky) - Left face hidden */}
<Prism positionX={posRight} baseHue="sky" highlightSide="left" />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
<div className="space-y-4">
<div className="bg-slate-50 p-4 rounded-lg border border-slate-200">
<h4 className="text-xs font-bold text-slate-400 uppercase mb-3">
Dimensions
</h4>
<div className="space-y-4">
<div>
<div className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Width (w)</span> <span>{w}</span>
</div>
<input
type="range"
min="40"
max="80"
value={w}
onChange={(e) => setW(parseInt(e.target.value))}
className="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600"
/>
</div>
<div>
<div className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Height (h)</span> <span>{h}</span>
</div>
<input
type="range"
min="40"
max="100"
value={h}
onChange={(e) => setH(parseInt(e.target.value))}
className="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600"
/>
</div>
<div>
<div className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Depth (d)</span> <span>{d}</span>
</div>
<input
type="range"
min="40"
max="80"
value={d}
onChange={(e) => setD(parseInt(e.target.value))}
className="w-full h-1 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600"
/>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div
className={`p-5 rounded-xl border transition-colors ${isMerged ? "bg-indigo-50 border-indigo-200" : "bg-slate-50 border-slate-200"}`}
>
<div className="text-xs uppercase font-bold text-slate-500 mb-2">
Total Surface Area
</div>
<div className="text-4xl font-mono font-bold text-slate-800 tracking-tight">
{isMerged ? mergedSA : totalSeparateSA}
</div>
<div className="text-sm mt-2 text-slate-600 font-medium">
{isMerged
? "⬇ Area decreased (Faces Hidden)"
: "Sum of 2 separated prisms"}
</div>
</div>
<div
className={`p-4 rounded-lg border flex justify-between items-center transition-colors ${isMerged ? "bg-rose-50 border-rose-200 opacity-50" : "bg-rose-50 border-rose-200"}`}
>
<span className="text-xs font-bold text-rose-800 uppercase">
Hidden Area Calculation
</span>
<div className="text-right">
<div className="font-mono font-bold text-rose-600 text-lg">
2 × ({d}×{h})
</div>
<div className="text-xs text-rose-700/70 font-bold">
= {2 * hiddenFaceArea} lost
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CompositeSolidsWidget;