diff --git a/frontend/src/components/cellDivision/CellDivisionScene.tsx b/frontend/src/components/cellDivision/CellDivisionScene.tsx index 2a3cb7f..237f1ca 100644 --- a/frontend/src/components/cellDivision/CellDivisionScene.tsx +++ b/frontend/src/components/cellDivision/CellDivisionScene.tsx @@ -2,15 +2,19 @@ import { useMemo, useRef } from "react"; import { useFrame } from "@react-three/fiber"; import * as THREE from "three"; import type { Stage } from "../../data/cellDivisionStages.js"; +import type { CellComponentId } from "../../data/cellDivisionComponents.js"; // Cena 3D ilustrativa (mesmo princípio do CellDiagram3D.tsx e do // Mannequin.tsx: sem malha real disponível pra esse conteúdo, então // desenhamos à mão com primitivas do Three.js) — recebe só o ESTÁGIO alvo // atual (`stage`) e anima suavemente toda célula/cromátide até lá sempre // que o estágio muda, via interpolação por frame (useFrame), sem precisar -// de nenhuma biblioteca de animação. +// de nenhuma biblioteca de animação. Também aceita clique nos componentes +// (cromossomo, centrossomo, fibra) — ver `onSelect`. const MAX_CELLS = 4; +const HIGHLIGHT = new THREE.Color("#22d3ee"); +const BLACK = new THREE.Color(0x000000); // Fator de suavização independente de framerate: a cada frame, anda uma // fração `k` do caminho que falta até o alvo — converge suave em ~0.4s @@ -19,20 +23,31 @@ function smoothing(delta: number, halfLifeSeconds = 0.18) { return 1 - Math.pow(0.5, delta / halfLifeSeconds); } -function ChromatidShape({ color }: { color: string }) { +interface SelectionProps { + selected: CellComponentId | null; + onSelect: (id: CellComponentId) => void; +} + +function ChromatidShape({ color, isSelected }: { color: string; isSelected: boolean }) { + const matProps = { + color, + roughness: 0.5, + emissive: isSelected ? HIGHLIGHT : BLACK, + emissiveIntensity: isSelected ? 0.8 : 0, + }; return ( - + - + - + ); @@ -44,7 +59,15 @@ interface ChromatidTargetWorld { color: string; } -function AnimatedChromatid({ id, targets }: { id: string; targets: Map }) { +function AnimatedChromatid({ + id, + targets, + selected, + onSelect, +}: { + id: string; + targets: Map; +} & SelectionProps) { // Posição/rotação são controladas 100% via useFrame (nunca como prop // JSX reativa) — se passássemos `position={...}` no JSX, o // react-three-fiber reaplicaria (e "teleportaria") o objeto pra lá toda @@ -65,8 +88,14 @@ function AnimatedChromatid({ id, targets }: { id: string; targets: Map - + { + e.stopPropagation(); + onSelect("cromossomo"); + }} + > + ); } @@ -83,6 +112,8 @@ interface CellSlotState { function AnimatedCellSlot({ target, state, + selected, + onSelect, }: { target: { center: [number, number, number]; @@ -93,7 +124,7 @@ function AnimatedCellSlot({ elongated: number; } | null; state: CellSlotState; -}) { +} & SelectionProps) { const groupRef = useRef(null); const membraneRef = useRef(null); const membraneMatRef = useRef(null); @@ -139,9 +170,17 @@ function AnimatedCellSlot({ } }); + const centrosomeSelected = selected === "centrossomo"; + const fiberSelected = selected === "fibras"; + return ( - {/* membrana plasmática — translúcida, não recebe clique (só decorativa) */} + {/* membrana plasmática e envelope nuclear: sem raycast (`raycast={() => + null}`) de propósito — sendo "cascas" que envolvem tudo o resto, + elas sempre seriam o primeiro acerto de qualquer raio de clique, + impedindo o clique de alcançar cromossomo/centrossomo/fibra por + dentro. Continuam explicáveis pela lista de componentes no + painel (CellDivisionPage.tsx), só não são clicáveis AQUI. */} null}> - {/* envelope nuclear — esfera um pouco menor, desfaz/refaz conforme a fase */} null}> @@ -163,9 +201,20 @@ function AnimatedCellSlot({ da prófase. */} {[-1, 1].map((side) => ( - + { + e.stopPropagation(); + onSelect("centrossomo"); + }} + > - + ))} @@ -178,9 +227,21 @@ function AnimatedCellSlot({ key={i} position={[side * 0.65 + side * -0.32, offset * 0.6, offset]} rotation={[0, 0, side > 0 ? Math.PI / 2 + offset * 0.3 : -Math.PI / 2 + offset * 0.3]} + onClick={(e) => { + e.stopPropagation(); + onSelect("fibras"); + }} > - - + {/* raio um pouco maior só pra facilitar o clique — a fibra + visual continua fina, isso é puramente a área alvo */} + + ))} @@ -190,7 +251,7 @@ function AnimatedCellSlot({ ); } -export function CellDivisionScene({ stage }: { stage: Stage }) { +export function CellDivisionScene({ stage, selected, onSelect }: { stage: Stage } & SelectionProps) { // Estado imperativo (fora do React) por slot de célula — persiste entre // mudanças de estágio pra animação continuar de onde estava, sem exigir // que o número de células seja o mesmo do estágio anterior. @@ -235,11 +296,11 @@ export function CellDivisionScene({ stage }: { stage: Stage }) { {cellStates.map((state, i) => ( - + ))} {chromatidIds.map((id) => ( - + ))} ); diff --git a/frontend/src/data/cellDivisionComponents.ts b/frontend/src/data/cellDivisionComponents.ts new file mode 100644 index 0000000..985cb85 --- /dev/null +++ b/frontend/src/data/cellDivisionComponents.ts @@ -0,0 +1,67 @@ +// Glossário dos componentes que aparecem na cena 3D de mitose/meiose — +// clicável tanto direto no modelo 3D (cromossomos, centrossomos, fibras) +// quanto pela lista no painel (que também cobre membrana e envelope +// nuclear, que por design não recebem clique 3D — ver nota em +// CellDivisionScene.tsx sobre por que a "casca" que envolve tudo não pode +// ter raycast, senão intercepta o clique destinado ao que está dentro). +export type CellComponentId = + | "membrana" + | "envelope" + | "cromossomo" + | "centromero" + | "centrossomo" + | "fibras"; + +export interface CellComponentInfo { + id: CellComponentId; + label: string; + clickable3d: boolean; + description: string; +} + +export const CELL_COMPONENTS: CellComponentInfo[] = [ + { + id: "membrana", + label: "Membrana Plasmática", + clickable3d: false, + description: + "Envolve toda a célula e controla o que entra e sai dela. Na citocinese, ela se estrangula ao meio (sulco de clivagem) até se dividir em duas membranas independentes, uma pra cada célula-filha.", + }, + { + id: "envelope", + label: "Envelope Nuclear", + clickable3d: false, + description: + "Membrana dupla que separa o DNA do restante da célula durante a interfase. Se desfaz na prófase — só assim as fibras do fuso, que ficam no citoplasma, conseguem alcançar os cromossomos — e se reforma na telófase ao redor de cada novo conjunto de cromossomos.", + }, + { + id: "cromossomo", + label: "Cromossomo (cromátide)", + clickable3d: true, + description: + "DNA condensado e enrolado em proteínas (histonas), carregando os genes. Cada cor no modelo representa um cromossomo diferente. Quando duplicado (fase S), tem 2 cromátides-irmãs idênticas — o \"X\" clássico; depois de se separarem (anáfase), cada cromátide passa a ser um cromossomo independente.", + }, + { + id: "centromero", + label: "Centrômero", + clickable3d: false, + description: + "A região que mantém as duas cromátides-irmãs de um cromossomo duplicado unidas (o \"cruzamento\" do X). É ali que as fibras do fuso se prendem (numa estrutura chamada cinetócoro) pra puxar cada cromátide pra um polo na anáfase.", + }, + { + id: "centrossomo", + label: "Centrossomo", + clickable3d: true, + description: + "Organiza os microtúbulos da célula. Se duplica na interfase (por isso aparecem 2, desde a G2) e migra pra polos opostos, virando o ponto de partida das fibras do fuso.", + }, + { + id: "fibras", + label: "Fibras do Fuso (microtúbulos)", + clickable3d: true, + description: + "\"Cabos\" de microtúbulos que saem dos centrossomos e se prendem ao centrômero de cada cromossomo. São elas que empurram/puxam os cromossomos — primeiro alinhando-os na placa metafásica, depois puxando as cromátides-irmãs (ou os homólogos, na Meiose I) pra polos opostos na anáfase.", + }, +]; + +export const CELL_COMPONENT_BY_ID = new Map(CELL_COMPONENTS.map((c) => [c.id, c])); diff --git a/frontend/src/routes/CellDivisionPage.tsx b/frontend/src/routes/CellDivisionPage.tsx index 1872560..c307db9 100644 --- a/frontend/src/routes/CellDivisionPage.tsx +++ b/frontend/src/routes/CellDivisionPage.tsx @@ -9,6 +9,7 @@ import { MEIOSIS_II_ERROR_STAGES, } from "../data/cellDivisionStages.js"; import { GENETIC_SYNDROMES, ERROR_MECHANISM_LABELS, type ErrorMechanism } from "../data/geneticSyndromes.js"; +import { CELL_COMPONENTS, CELL_COMPONENT_BY_ID, type CellComponentId } from "../data/cellDivisionComponents.js"; type Mode = "mitose" | "meiose" | "erros"; type ErrorType = "meiose1" | "meiose2"; @@ -69,6 +70,7 @@ export default function CellDivisionPage() { const [leftOpen, setLeftOpen] = useState(false); const [rightOpen, setRightOpen] = useState(false); const [showSyndromeModal, setShowSyndromeModal] = useState(false); + const [selectedComponent, setSelectedComponent] = useState(null); const stages = useMemo(() => { if (mode === "mitose") return MITOSIS_STAGES; @@ -250,7 +252,7 @@ export default function CellDivisionPage() { - + @@ -320,6 +322,35 @@ export default function CellDivisionPage() {

{stage.label}

{stage.description}

+
+

+ Componentes da célula — clique num deles (na cena ou aqui) +

+
+ {CELL_COMPONENTS.map((c) => ( + + ))} +
+ {selectedComponent && ( +
+

{CELL_COMPONENT_BY_ID.get(selectedComponent)?.label}

+

+ {CELL_COMPONENT_BY_ID.get(selectedComponent)?.description} +

+
+ )} +
+ {mode === "erros" && (

Síndromes ligadas a este erro

diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index b4aed43..3e2667f 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/celldiagram3d.tsx","./src/components/clippingcontrols.tsx","./src/components/cookieconsent.tsx","./src/components/feedbackwidget.tsx","./src/components/layertoggle.tsx","./src/components/layout.tsx","./src/components/microscopydiagram.tsx","./src/components/onlinecounter.tsx","./src/components/quickregionnav.tsx","./src/components/searchnav.tsx","./src/components/skindiagram3d.tsx","./src/components/structureinfopanel.tsx","./src/components/celldivision/celldivisionscene.tsx","./src/components/viewer/exammarkers.tsx","./src/components/viewer/mannequin.tsx","./src/components/viewer/scene.tsx","./src/components/viewer/systemmodel.tsx","./src/data/celldivisionstages.ts","./src/data/geneticsyndromes.ts","./src/data/microscopycredits.ts","./src/data/microscopyhotspots.ts","./src/data/musclereferenceimages.ts","./src/data/physicalexam.ts","./src/hooks/usestructures.ts","./src/hooks/usesystemstructures.ts","./src/lib/analytics.ts","./src/lib/api.ts","./src/routes/adminstatspage.tsx","./src/routes/atlaspage.tsx","./src/routes/celldivisionpage.tsx","./src/routes/physicalexampage.tsx","./src/routes/planespage.tsx","./src/routes/quizpage.tsx","./src/store/useatlasstore.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/celldiagram3d.tsx","./src/components/clippingcontrols.tsx","./src/components/cookieconsent.tsx","./src/components/feedbackwidget.tsx","./src/components/layertoggle.tsx","./src/components/layout.tsx","./src/components/microscopydiagram.tsx","./src/components/onlinecounter.tsx","./src/components/quickregionnav.tsx","./src/components/searchnav.tsx","./src/components/skindiagram3d.tsx","./src/components/structureinfopanel.tsx","./src/components/celldivision/celldivisionscene.tsx","./src/components/viewer/exammarkers.tsx","./src/components/viewer/mannequin.tsx","./src/components/viewer/scene.tsx","./src/components/viewer/systemmodel.tsx","./src/data/celldivisioncomponents.ts","./src/data/celldivisionstages.ts","./src/data/geneticsyndromes.ts","./src/data/microscopycredits.ts","./src/data/microscopyhotspots.ts","./src/data/musclereferenceimages.ts","./src/data/physicalexam.ts","./src/hooks/usestructures.ts","./src/hooks/usesystemstructures.ts","./src/lib/analytics.ts","./src/lib/api.ts","./src/routes/adminstatspage.tsx","./src/routes/atlaspage.tsx","./src/routes/celldivisionpage.tsx","./src/routes/physicalexampage.tsx","./src/routes/planespage.tsx","./src/routes/quizpage.tsx","./src/store/useatlasstore.ts"],"version":"5.9.3"} \ No newline at end of file