componentes da celula
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
1 parent
84ee99aabf
commit
3195bdd724
4 files changed
+179
-20
No files matched your search
@@ -2,15 +2,19 @@ import { useMemo, useRef } from "react";
|
|||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type { Stage } from "../../data/cellDivisionStages.js";
|
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
|
// Cena 3D ilustrativa (mesmo princípio do CellDiagram3D.tsx e do
|
||||||
// Mannequin.tsx: sem malha real disponível pra esse conteúdo, então
|
// 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
|
// 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
|
// 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
|
// 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 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
|
// 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
|
// 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);
|
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 (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<mesh position={[-0.045, 0.08, 0]} rotation={[0, 0, Math.PI / 7]}>
|
<mesh position={[-0.045, 0.08, 0]} rotation={[0, 0, Math.PI / 7]}>
|
||||||
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
||||||
<meshStandardMaterial color={color} roughness={0.5} />
|
<meshStandardMaterial {...matProps} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<mesh position={[0.045, 0.08, 0]} rotation={[0, 0, -Math.PI / 7]}>
|
<mesh position={[0.045, 0.08, 0]} rotation={[0, 0, -Math.PI / 7]}>
|
||||||
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
<capsuleGeometry args={[0.022, 0.14, 3, 6]} />
|
||||||
<meshStandardMaterial color={color} roughness={0.5} />
|
<meshStandardMaterial {...matProps} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<mesh>
|
<mesh>
|
||||||
<sphereGeometry args={[0.03, 10, 10]} />
|
<sphereGeometry args={[0.03, 10, 10]} />
|
||||||
<meshStandardMaterial color={color} roughness={0.5} />
|
<meshStandardMaterial {...matProps} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
@@ -44,7 +59,15 @@ interface ChromatidTargetWorld {
|
|||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AnimatedChromatid({ id, targets }: { id: string; targets: Map<string, ChromatidTargetWorld> }) {
|
function AnimatedChromatid({
|
||||||
|
id,
|
||||||
|
targets,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
targets: Map<string, ChromatidTargetWorld>;
|
||||||
|
} & SelectionProps) {
|
||||||
// Posição/rotação são controladas 100% via useFrame (nunca como prop
|
// Posição/rotação são controladas 100% via useFrame (nunca como prop
|
||||||
// JSX reativa) — se passássemos `position={...}` no JSX, o
|
// JSX reativa) — se passássemos `position={...}` no JSX, o
|
||||||
// react-three-fiber reaplicaria (e "teleportaria") o objeto pra lá toda
|
// react-three-fiber reaplicaria (e "teleportaria") o objeto pra lá toda
|
||||||
@@ -65,8 +88,14 @@ function AnimatedChromatid({ id, targets }: { id: string; targets: Map<string, C
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={ref}>
|
<group
|
||||||
<ChromatidShape color={initialColor} />
|
ref={ref}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSelect("cromossomo");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChromatidShape color={initialColor} isSelected={selected === "cromossomo"} />
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,6 +112,8 @@ interface CellSlotState {
|
|||||||
function AnimatedCellSlot({
|
function AnimatedCellSlot({
|
||||||
target,
|
target,
|
||||||
state,
|
state,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
target: {
|
target: {
|
||||||
center: [number, number, number];
|
center: [number, number, number];
|
||||||
@@ -93,7 +124,7 @@ function AnimatedCellSlot({
|
|||||||
elongated: number;
|
elongated: number;
|
||||||
} | null;
|
} | null;
|
||||||
state: CellSlotState;
|
state: CellSlotState;
|
||||||
}) {
|
} & SelectionProps) {
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const membraneRef = useRef<THREE.Mesh>(null);
|
const membraneRef = useRef<THREE.Mesh>(null);
|
||||||
const membraneMatRef = useRef<THREE.MeshStandardMaterial>(null);
|
const membraneMatRef = useRef<THREE.MeshStandardMaterial>(null);
|
||||||
@@ -139,9 +170,17 @@ function AnimatedCellSlot({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const centrosomeSelected = selected === "centrossomo";
|
||||||
|
const fiberSelected = selected === "fibras";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={groupRef}>
|
<group ref={groupRef}>
|
||||||
{/* 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. */}
|
||||||
<mesh ref={membraneRef} raycast={() => null}>
|
<mesh ref={membraneRef} raycast={() => null}>
|
||||||
<sphereGeometry args={[0.8, 28, 28]} />
|
<sphereGeometry args={[0.8, 28, 28]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
@@ -153,7 +192,6 @@ function AnimatedCellSlot({
|
|||||||
depthWrite={false}
|
depthWrite={false}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* envelope nuclear — esfera um pouco menor, desfaz/refaz conforme a fase */}
|
|
||||||
<mesh raycast={() => null}>
|
<mesh raycast={() => null}>
|
||||||
<sphereGeometry args={[0.42, 24, 24]} />
|
<sphereGeometry args={[0.42, 24, 24]} />
|
||||||
<meshStandardMaterial ref={envelopeMatRef} color="#c4b5fd" transparent opacity={0} depthWrite={false} />
|
<meshStandardMaterial ref={envelopeMatRef} color="#c4b5fd" transparent opacity={0} depthWrite={false} />
|
||||||
@@ -163,9 +201,20 @@ function AnimatedCellSlot({
|
|||||||
da prófase. */}
|
da prófase. */}
|
||||||
<group ref={centrosomesRef}>
|
<group ref={centrosomesRef}>
|
||||||
{[-1, 1].map((side) => (
|
{[-1, 1].map((side) => (
|
||||||
<mesh key={side} position={[side * 0.65, 0, 0]}>
|
<mesh
|
||||||
|
key={side}
|
||||||
|
position={[side * 0.65, 0, 0]}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSelect("centrossomo");
|
||||||
|
}}
|
||||||
|
>
|
||||||
<sphereGeometry args={[0.035, 10, 10]} />
|
<sphereGeometry args={[0.035, 10, 10]} />
|
||||||
<meshStandardMaterial color="#f8fafc" />
|
<meshStandardMaterial
|
||||||
|
color="#f8fafc"
|
||||||
|
emissive={centrosomeSelected ? HIGHLIGHT : BLACK}
|
||||||
|
emissiveIntensity={centrosomeSelected ? 0.8 : 0}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
@@ -178,9 +227,21 @@ function AnimatedCellSlot({
|
|||||||
key={i}
|
key={i}
|
||||||
position={[side * 0.65 + side * -0.32, offset * 0.6, offset]}
|
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]}
|
rotation={[0, 0, side > 0 ? Math.PI / 2 + offset * 0.3 : -Math.PI / 2 + offset * 0.3]}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSelect("fibras");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<cylinderGeometry args={[0.004, 0.004, 0.64, 4]} />
|
{/* raio um pouco maior só pra facilitar o clique — a fibra
|
||||||
<meshStandardMaterial color="#f8fafc" transparent opacity={0.4} />
|
visual continua fina, isso é puramente a área alvo */}
|
||||||
|
<cylinderGeometry args={[0.02, 0.02, 0.64, 6]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
color="#f8fafc"
|
||||||
|
transparent
|
||||||
|
opacity={fiberSelected ? 0.9 : 0.4}
|
||||||
|
emissive={fiberSelected ? HIGHLIGHT : BLACK}
|
||||||
|
emissiveIntensity={fiberSelected ? 0.6 : 0}
|
||||||
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
@@ -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
|
// 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
|
// 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.
|
// que o número de células seja o mesmo do estágio anterior.
|
||||||
@@ -235,11 +296,11 @@ export function CellDivisionScene({ stage }: { stage: Stage }) {
|
|||||||
<directionalLight position={[-2, -1, -2]} intensity={0.3} />
|
<directionalLight position={[-2, -1, -2]} intensity={0.3} />
|
||||||
|
|
||||||
{cellStates.map((state, i) => (
|
{cellStates.map((state, i) => (
|
||||||
<AnimatedCellSlot key={i} target={stage.cells[i] ?? null} state={state} />
|
<AnimatedCellSlot key={i} target={stage.cells[i] ?? null} state={state} selected={selected} onSelect={onSelect} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{chromatidIds.map((id) => (
|
{chromatidIds.map((id) => (
|
||||||
<AnimatedChromatid key={id} id={id} targets={chromatidTargets} />
|
<AnimatedChromatid key={id} id={id} targets={chromatidTargets} selected={selected} onSelect={onSelect} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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]));
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
MEIOSIS_II_ERROR_STAGES,
|
MEIOSIS_II_ERROR_STAGES,
|
||||||
} from "../data/cellDivisionStages.js";
|
} from "../data/cellDivisionStages.js";
|
||||||
import { GENETIC_SYNDROMES, ERROR_MECHANISM_LABELS, type ErrorMechanism } from "../data/geneticSyndromes.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 Mode = "mitose" | "meiose" | "erros";
|
||||||
type ErrorType = "meiose1" | "meiose2";
|
type ErrorType = "meiose1" | "meiose2";
|
||||||
@@ -69,6 +70,7 @@ export default function CellDivisionPage() {
|
|||||||
const [leftOpen, setLeftOpen] = useState(false);
|
const [leftOpen, setLeftOpen] = useState(false);
|
||||||
const [rightOpen, setRightOpen] = useState(false);
|
const [rightOpen, setRightOpen] = useState(false);
|
||||||
const [showSyndromeModal, setShowSyndromeModal] = useState(false);
|
const [showSyndromeModal, setShowSyndromeModal] = useState(false);
|
||||||
|
const [selectedComponent, setSelectedComponent] = useState<CellComponentId | null>(null);
|
||||||
|
|
||||||
const stages = useMemo(() => {
|
const stages = useMemo(() => {
|
||||||
if (mode === "mitose") return MITOSIS_STAGES;
|
if (mode === "mitose") return MITOSIS_STAGES;
|
||||||
@@ -250,7 +252,7 @@ export default function CellDivisionPage() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Canvas dpr={[1, 1.5]} camera={{ position: [0, 1.4, 5.4], fov: 42 }}>
|
<Canvas dpr={[1, 1.5]} camera={{ position: [0, 1.4, 5.4], fov: 42 }}>
|
||||||
<CellDivisionScene stage={stage} />
|
<CellDivisionScene stage={stage} selected={selectedComponent} onSelect={setSelectedComponent} />
|
||||||
<CameraControls makeDefault minDistance={2} maxDistance={12} />
|
<CameraControls makeDefault minDistance={2} maxDistance={12} />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
@@ -320,6 +322,35 @@ export default function CellDivisionPage() {
|
|||||||
<h2 className="text-lg font-semibold text-white leading-snug">{stage.label}</h2>
|
<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>
|
<p className="text-sm text-slate-300 leading-relaxed">{stage.description}</p>
|
||||||
|
|
||||||
|
<div className="pt-3 border-t border-white/10 space-y-2">
|
||||||
|
<p className="text-[11px] uppercase tracking-wide text-slate-500">
|
||||||
|
Componentes da célula <span className="normal-case text-slate-600">— clique num deles (na cena ou aqui)</span>
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{CELL_COMPONENTS.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c.id}
|
||||||
|
onClick={() => setSelectedComponent(c.id)}
|
||||||
|
className={`text-xs px-2.5 py-1.5 rounded-full border ${
|
||||||
|
selectedComponent === c.id
|
||||||
|
? "bg-accent text-white border-accent"
|
||||||
|
: "border-white/10 bg-white/5 text-slate-300 hover:bg-white/10"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{c.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{selectedComponent && (
|
||||||
|
<div className="rounded-lg border border-accent/30 bg-accent/10 p-2.5 mt-1">
|
||||||
|
<p className="text-sm font-medium text-white">{CELL_COMPONENT_BY_ID.get(selectedComponent)?.label}</p>
|
||||||
|
<p className="text-xs text-slate-300 mt-1 leading-relaxed">
|
||||||
|
{CELL_COMPONENT_BY_ID.get(selectedComponent)?.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{mode === "erros" && (
|
{mode === "erros" && (
|
||||||
<div className="pt-3 border-t border-white/10 space-y-2">
|
<div className="pt-3 border-t border-white/10 space-y-2">
|
||||||
<p className="text-[11px] uppercase tracking-wide text-slate-500">Síndromes ligadas a este erro</p>
|
<p className="text-[11px] uppercase tracking-wide text-slate-500">Síndromes ligadas a este erro</p>
|
||||||
|
|||||||
@@ -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"}
|
{"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"}
|
||||||
Reference in new issue
Block a user