feat(lessons): add lessons from client db
This commit is contained in:
255
src/components/lessons/CompositeSolidsWidget.tsx
Normal file
255
src/components/lessons/CompositeSolidsWidget.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user