mitose e meiose
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
1 parent
a972670b7d
commit
f32833c023
6 files changed
+778
-1
No files matched your search
@@ -0,0 +1,241 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import { CellDivisionScene } from "../components/cellDivision/CellDivisionScene.js";
|
||||
import { MITOSIS_STAGES, MEIOSIS_STAGES } from "../data/cellDivisionStages.js";
|
||||
|
||||
type Mode = "mitose" | "meiose";
|
||||
|
||||
const AUTOPLAY_MS = 3200;
|
||||
|
||||
export default function CellDivisionPage() {
|
||||
const [mode, setMode] = useState<Mode>("mitose");
|
||||
const [stageIndex, setStageIndex] = useState(0);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [leftOpen, setLeftOpen] = useState(false);
|
||||
const [rightOpen, setRightOpen] = useState(false);
|
||||
|
||||
const stages = mode === "mitose" ? MITOSIS_STAGES : MEIOSIS_STAGES;
|
||||
const stage = stages[stageIndex];
|
||||
|
||||
// Trocar de modo reinicia o estágio — misturar índice de mitose (7
|
||||
// estágios) com o array de meiose (9 estágios) fora de ordem não faz
|
||||
// sentido pedagogicamente.
|
||||
function changeMode(next: Mode) {
|
||||
setMode(next);
|
||||
setStageIndex(0);
|
||||
setPlaying(false);
|
||||
}
|
||||
|
||||
function goTo(index: number) {
|
||||
setStageIndex(Math.max(0, Math.min(stages.length - 1, index)));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!playing) return;
|
||||
const id = setInterval(() => {
|
||||
setStageIndex((i) => {
|
||||
if (i >= stages.length - 1) {
|
||||
setPlaying(false);
|
||||
return i;
|
||||
}
|
||||
return i + 1;
|
||||
});
|
||||
}, AUTOPLAY_MS);
|
||||
return () => clearInterval(id);
|
||||
}, [playing, stages.length]);
|
||||
|
||||
// Reinicia autoplay do começo se já estava no fim.
|
||||
function togglePlay() {
|
||||
setPlaying((p) => {
|
||||
if (!p && stageIndex >= stages.length - 1) setStageIndex(0);
|
||||
return !p;
|
||||
});
|
||||
}
|
||||
|
||||
const cellCount = stage.cells.length;
|
||||
|
||||
return (
|
||||
<div className="h-full relative md:grid md:grid-cols-[260px_1fr_320px]">
|
||||
<aside
|
||||
className={`fixed md:static inset-y-0 left-0 z-30 w-[86vw] max-w-[280px] md:w-auto
|
||||
border-r border-white/10 bg-panel p-4 space-y-4 overflow-y-auto
|
||||
transition-transform duration-200 md:translate-x-0
|
||||
${leftOpen ? "translate-x-0" : "-translate-x-full"}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setLeftOpen(false)}
|
||||
className="md:hidden float-right text-slate-400 hover:text-white text-sm px-2 py-1"
|
||||
aria-label="Fechar painel"
|
||||
>
|
||||
✕ Fechar
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Processo</h3>
|
||||
<div className="flex rounded-lg overflow-hidden border border-white/10">
|
||||
<button
|
||||
onClick={() => changeMode("mitose")}
|
||||
className={`flex-1 text-sm py-2 ${mode === "mitose" ? "bg-accent text-white" : "bg-white/5 text-slate-300 hover:bg-white/10"}`}
|
||||
>
|
||||
Mitose
|
||||
</button>
|
||||
<button
|
||||
onClick={() => changeMode("meiose")}
|
||||
className={`flex-1 text-sm py-2 ${mode === "meiose" ? "bg-accent text-white" : "bg-white/5 text-slate-300 hover:bg-white/10"}`}
|
||||
>
|
||||
Meiose
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[11px] text-slate-500 mt-2 leading-relaxed">
|
||||
{mode === "mitose"
|
||||
? "1 célula (2n=4, simplificado) → 2 células-filhas idênticas."
|
||||
: "1 célula (2n=4) → 4 células-filhas haploides, geneticamente diferentes entre si."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Fases</h3>
|
||||
<ol className="space-y-1">
|
||||
{stages.map((s, i) => (
|
||||
<li key={s.id}>
|
||||
<button
|
||||
onClick={() => {
|
||||
goTo(i);
|
||||
setPlaying(false);
|
||||
}}
|
||||
className={`w-full text-left text-sm px-2.5 py-1.5 rounded flex items-center gap-2 ${
|
||||
i === stageIndex ? "bg-accent text-white" : "text-slate-300 hover:bg-white/5"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-flex items-center justify-center w-5 h-5 rounded-full text-[10px] shrink-0 ${
|
||||
i === stageIndex ? "bg-white/25" : "bg-white/10"
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
{s.label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{(leftOpen || rightOpen) && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-20 md:hidden"
|
||||
onClick={() => {
|
||||
setLeftOpen(false);
|
||||
setRightOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="relative h-full min-h-0">
|
||||
<button
|
||||
onClick={() => setLeftOpen(true)}
|
||||
className="md:hidden absolute top-3 left-3 z-10 bg-panel/90 border border-white/10 rounded-lg px-3 py-2 text-xs shadow-lg"
|
||||
>
|
||||
☰ Fases
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRightOpen(true)}
|
||||
className="md:hidden absolute top-3 right-3 z-10 bg-panel/90 border border-white/10 rounded-lg px-3 py-2 text-xs shadow-lg"
|
||||
>
|
||||
📖 Detalhes
|
||||
</button>
|
||||
|
||||
<Canvas dpr={[1, 1.5]} camera={{ position: [0, 1.4, 5.4], fov: 42 }}>
|
||||
<CellDivisionScene stage={stage} />
|
||||
<CameraControls makeDefault minDistance={2} maxDistance={12} />
|
||||
</Canvas>
|
||||
|
||||
{/* Controles de reprodução — barra fixa embaixo do canvas */}
|
||||
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 z-10 flex items-center gap-2 bg-panel/90 border border-white/10 rounded-full px-3 py-2 shadow-lg">
|
||||
<button
|
||||
onClick={() => {
|
||||
goTo(stageIndex - 1);
|
||||
setPlaying(false);
|
||||
}}
|
||||
disabled={stageIndex === 0}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full text-slate-300 hover:bg-white/10 disabled:opacity-30 disabled:hover:bg-transparent"
|
||||
aria-label="Fase anterior"
|
||||
>
|
||||
⏮
|
||||
</button>
|
||||
<button
|
||||
onClick={togglePlay}
|
||||
className="w-9 h-9 flex items-center justify-center rounded-full bg-accent text-white hover:brightness-110"
|
||||
aria-label={playing ? "Pausar" : "Reproduzir"}
|
||||
>
|
||||
{playing ? "⏸" : "▶"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
goTo(stageIndex + 1);
|
||||
setPlaying(false);
|
||||
}}
|
||||
disabled={stageIndex === stages.length - 1}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full text-slate-300 hover:bg-white/10 disabled:opacity-30 disabled:hover:bg-transparent"
|
||||
aria-label="Próxima fase"
|
||||
>
|
||||
⏭
|
||||
</button>
|
||||
<span className="text-xs text-slate-400 px-1 tabular-nums">
|
||||
{stageIndex + 1}/{stages.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-3 right-3 z-10 hidden md:block bg-panel/90 border border-white/10 rounded-lg px-3 py-1.5 text-xs text-slate-300 shadow-lg">
|
||||
{cellCount} {cellCount === 1 ? "célula" : "células"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside
|
||||
className={`fixed md:static inset-y-0 right-0 z-30 w-[86vw] max-w-[320px] md:w-auto
|
||||
border-l border-white/10 bg-panel p-4 overflow-y-auto space-y-3
|
||||
transition-transform duration-200 md:translate-x-0
|
||||
${rightOpen ? "translate-x-0" : "translate-x-full"}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => setRightOpen(false)}
|
||||
className="md:hidden text-slate-400 hover:text-white text-sm px-2 py-1"
|
||||
aria-label="Fechar painel"
|
||||
>
|
||||
✕ Fechar
|
||||
</button>
|
||||
<span className="inline-block text-xs px-2 py-1 rounded-full bg-accent/20 text-accent">
|
||||
{mode === "mitose" ? "Mitose" : "Meiose"}
|
||||
</span>
|
||||
<h2 className="text-lg font-semibold text-white leading-snug">{stage.label}</h2>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">{stage.description}</p>
|
||||
|
||||
<div className="pt-3 border-t border-white/10 space-y-1.5">
|
||||
<p className="text-[11px] uppercase tracking-wide text-slate-500">Legenda das cores</p>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<span className="w-2.5 h-2.5 rounded-full inline-block" style={{ background: "#4f7cff" }} />
|
||||
Cromossomo 1 (materno) <span className="text-slate-600">— homólogo do ciano</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<span className="w-2.5 h-2.5 rounded-full inline-block" style={{ background: "#22d3ee" }} />
|
||||
Cromossomo 1 (paterno)
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<span className="w-2.5 h-2.5 rounded-full inline-block" style={{ background: "#f59e0b" }} />
|
||||
Cromossomo 2 (materno) <span className="text-slate-600">— homólogo do amarelo</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-400">
|
||||
<span className="w-2.5 h-2.5 rounded-full inline-block" style={{ background: "#eab308" }} />
|
||||
Cromossomo 2 (paterno)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[11px] text-slate-500 pt-2 border-t border-white/10 leading-relaxed">
|
||||
Modelo ilustrativo e simplificado (2n=4 em vez dos 46 cromossomos humanos reais) — pensado pra mostrar os conceitos-chave de cada fase, não uma simulação biofísica exata. Arraste pra girar, role pra dar zoom.
|
||||
</p>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user