refatoraçao citologia e histologia
No files matched your search
@@ -16,7 +16,7 @@ import path from "node:path";
|
|||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
function structure(slug, namePt, nameEn, description) {
|
function structure(slug, namePt, nameEn, description, regionPath = ["Citologia", "Célula (modelo ilustrativo)"]) {
|
||||||
return {
|
return {
|
||||||
system: "citologia",
|
system: "citologia",
|
||||||
slug,
|
slug,
|
||||||
@@ -24,12 +24,20 @@ function structure(slug, namePt, nameEn, description) {
|
|||||||
nameEn,
|
nameEn,
|
||||||
synonyms: [],
|
synonyms: [],
|
||||||
description,
|
description,
|
||||||
regionPath: ["Citologia", "Célula (modelo ilustrativo)"],
|
regionPath,
|
||||||
modelFile: "n/a",
|
modelFile: "n/a",
|
||||||
meshName: slug,
|
meshName: slug,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Galeria de tipos celulares especializados — cada um ilustrativo (mesmo
|
||||||
|
// princípio da célula genérica acima: sem malha 3D real disponível pra
|
||||||
|
// isso), mostrando o que torna aquele tipo reconhecível/diferente de uma
|
||||||
|
// célula genérica. Ver frontend/src/routes/CellTypesPage.tsx.
|
||||||
|
function cellType(group, slug, namePt, nameEn, description) {
|
||||||
|
return structure(slug, namePt, nameEn, description, ["Citologia", "Tipos Celulares", group]);
|
||||||
|
}
|
||||||
|
|
||||||
const structures = [
|
const structures = [
|
||||||
structure(
|
structure(
|
||||||
"celula-membrana-plasmatica",
|
"celula-membrana-plasmatica",
|
||||||
@@ -85,6 +93,132 @@ const structures = [
|
|||||||
"Lysosomes",
|
"Lysosomes",
|
||||||
"Vesículas com enzimas digestivas (hidrolases ácidas) que degradam material vindo de dentro ou de fora da célula — o \"sistema de reciclagem e digestão\" celular.",
|
"Vesículas com enzimas digestivas (hidrolases ácidas) que degradam material vindo de dentro ou de fora da célula — o \"sistema de reciclagem e digestão\" celular.",
|
||||||
),
|
),
|
||||||
|
structure(
|
||||||
|
"celula-centriolos",
|
||||||
|
"Centríolos",
|
||||||
|
"Centrioles",
|
||||||
|
"Par de estruturas cilíndricas (feitas de microtúbulos dispostos em 9 trincas) perto do núcleo, formando o centrossomo. Se duplicam na interfase e migram para os polos opostos antes da divisão celular, organizando as fibras do fuso mitótico/meiótico — ver o módulo de Mitose e Meiose.",
|
||||||
|
),
|
||||||
|
structure(
|
||||||
|
"celula-citoesqueleto",
|
||||||
|
"Citoesqueleto",
|
||||||
|
"Cytoskeleton",
|
||||||
|
"Rede de filamentos proteicos (microtúbulos, filamentos de actina e filamentos intermediários) que dá forma e sustentação interna à célula, organiza o posicionamento das organelas e serve de \"trilho\" para o transporte intracelular e para o movimento da própria célula.",
|
||||||
|
),
|
||||||
|
structure(
|
||||||
|
"celula-peroxissomos",
|
||||||
|
"Peroxissomos",
|
||||||
|
"Peroxisomes",
|
||||||
|
"Vesículas menores que os lisossomos, com enzimas oxidativas que degradam ácidos graxos de cadeia longa e neutralizam peróxido de hidrogênio (um subproduto tóxico dessas reações) usando a enzima catalase.",
|
||||||
|
),
|
||||||
|
|
||||||
|
// ---- Neurônio ----
|
||||||
|
cellType(
|
||||||
|
"Neurônio",
|
||||||
|
"neuronio-corpo-celular",
|
||||||
|
"Corpo Celular (Soma)",
|
||||||
|
"Cell Body (Soma)",
|
||||||
|
"Contém o núcleo e a maior parte das organelas do neurônio (retículo endoplasmático rugoso aqui é chamado de corpúsculo de Nissl); é onde a maior parte das proteínas e neurotransmissores é sintetizada.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Neurônio",
|
||||||
|
"neuronio-dendritos",
|
||||||
|
"Dendritos",
|
||||||
|
"Dendrites",
|
||||||
|
"Prolongamentos curtos e ramificados que recebem sinais de outros neurônios (nas sinapses) e os conduzem em direção ao corpo celular.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Neurônio",
|
||||||
|
"neuronio-axonio",
|
||||||
|
"Axônio",
|
||||||
|
"Axon",
|
||||||
|
"Prolongamento único, longo (pode chegar a mais de 1 metro nos maiores neurônios do corpo), que conduz o impulso nervoso do corpo celular até o terminal sináptico.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Neurônio",
|
||||||
|
"neuronio-bainha-mielina",
|
||||||
|
"Bainha de Mielina",
|
||||||
|
"Myelin Sheath",
|
||||||
|
"Camada isolante (produzida por células de Schwann no sistema nervoso periférico, ou oligodendrócitos no central) que envolve o axônio em segmentos, aumentando MUITO a velocidade de condução do impulso. Os intervalos sem mielina entre os segmentos são os nódulos de Ranvier — é ali que o impulso \"salta\" de um nódulo a outro (condução saltatória).",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Neurônio",
|
||||||
|
"neuronio-terminal-sinaptico",
|
||||||
|
"Terminal Sináptico",
|
||||||
|
"Synaptic Terminal",
|
||||||
|
"Extremidade do axônio, dilatada em botões sinápticos cheios de vesículas com neurotransmissor — é aqui que o sinal elétrico vira sinal químico, liberado na fenda sináptica para o próximo neurônio (ou célula-alvo).",
|
||||||
|
),
|
||||||
|
|
||||||
|
// ---- Hemácia (eritrócito) ----
|
||||||
|
cellType(
|
||||||
|
"Hemácia (Eritrócito)",
|
||||||
|
"hemacia-eritrocito",
|
||||||
|
"Hemácia (Eritrócito)",
|
||||||
|
"Red Blood Cell (Erythrocyte)",
|
||||||
|
"Célula mais numerosa do sangue, especializada em transportar oxigênio (ligado à hemoglobina). Formato de disco bicôncavo (aumenta a área de superfície para troca de gases) e, diferente de quase toda célula humana, NÃO TEM NÚCLEO nem a maioria das organelas na fase madura — são expelidos durante a formação, abrindo mais espaço pra hemoglobina.",
|
||||||
|
),
|
||||||
|
|
||||||
|
// ---- Célula Muscular Esquelética ----
|
||||||
|
cellType(
|
||||||
|
"Célula Muscular Esquelética",
|
||||||
|
"musculo-fibra",
|
||||||
|
"Fibra Muscular Esquelética",
|
||||||
|
"Skeletal Muscle Fiber",
|
||||||
|
"Célula longa e cilíndrica (uma \"fibra\"), formada pela fusão de várias células precursoras durante o desenvolvimento — por isso é MULTINUCLEADA. Contém miofibrilas (feixes de filamentos de actina e miosina) que se contraem sob comando do sistema nervoso.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Célula Muscular Esquelética",
|
||||||
|
"musculo-nucleos-perifericos",
|
||||||
|
"Núcleos Periféricos",
|
||||||
|
"Peripheral Nuclei",
|
||||||
|
"Vários núcleos (um resquício da fusão das células precursoras) posicionados na periferia da fibra, logo abaixo da membrana — diferente da maioria das células, que tem só 1 núcleo central.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Célula Muscular Esquelética",
|
||||||
|
"musculo-estriacoes",
|
||||||
|
"Estriações Transversais",
|
||||||
|
"Cross Striations",
|
||||||
|
"Padrão de faixas claras e escuras alternadas, visível ao microscópio, formado pela organização regular dos filamentos de actina e miosina em sarcômeros — é o que dá o nome \"músculo estriado\" (esquelético e cardíaco; o liso não tem essas faixas).",
|
||||||
|
),
|
||||||
|
|
||||||
|
// ---- Espermatozoide ----
|
||||||
|
cellType(
|
||||||
|
"Espermatozoide",
|
||||||
|
"espermatozoide-cabeca",
|
||||||
|
"Cabeça",
|
||||||
|
"Head",
|
||||||
|
"Contém o núcleo com o DNA extremamente condensado, coberto na metade anterior pelo acrossomo — uma vesícula com enzimas que digerem a camada externa do óvulo na fecundação.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Espermatozoide",
|
||||||
|
"espermatozoide-peca-intermediaria",
|
||||||
|
"Peça Intermediária",
|
||||||
|
"Midpiece",
|
||||||
|
"Repleta de mitocôndrias organizadas em espiral ao redor do flagelo — fornecem o ATP necessário para o batimento do flagelo durante toda a jornada até o óvulo.",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Espermatozoide",
|
||||||
|
"espermatozoide-flagelo",
|
||||||
|
"Flagelo (Cauda)",
|
||||||
|
"Flagellum (Tail)",
|
||||||
|
"Longo prolongamento que bate em movimento ondulatório, impulsionando o espermatozoide — a única célula humana com flagelo verdadeiro.",
|
||||||
|
),
|
||||||
|
|
||||||
|
// ---- Leucócito (neutrófilo como representante) ----
|
||||||
|
cellType(
|
||||||
|
"Leucócito (Neutrófilo)",
|
||||||
|
"leucocito-nucleo-lobulado",
|
||||||
|
"Núcleo Lobulado (Polimorfonuclear)",
|
||||||
|
"Lobulated Nucleus (Polymorphonuclear)",
|
||||||
|
"Núcleo dividido em 2 a 5 lóbulos conectados por filamentos finos de cromatina — a característica que dá ao neutrófilo (e a outros granulócitos) o nome \"polimorfonuclear\".",
|
||||||
|
),
|
||||||
|
cellType(
|
||||||
|
"Leucócito (Neutrófilo)",
|
||||||
|
"leucocito-granulos",
|
||||||
|
"Grânulos Citoplasmáticos",
|
||||||
|
"Cytoplasmic Granules",
|
||||||
|
"Pequenas vesículas espalhadas pelo citoplasma, cheias de enzimas antimicrobianas — são liberadas sobre bactérias fagocitadas (ou na área da infecção) como parte da resposta imune inata.",
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
|
|||||||
@@ -40,6 +40,36 @@
|
|||||||
// Medula espinhal de coelho. Autor: John Alan Elson. CC BY-SA 4.0.
|
// Medula espinhal de coelho. Autor: John Alan Elson. CC BY-SA 4.0.
|
||||||
// https://commons.wikimedia.org/wiki/File:Rabbitspinalcord100x1.jpg
|
// https://commons.wikimedia.org/wiki/File:Rabbitspinalcord100x1.jpg
|
||||||
//
|
//
|
||||||
|
// histologia/epitelio-simples-pavimentoso.jpg
|
||||||
|
// Montagem total (whole mount), padrão "pedra de calçamento" típico do
|
||||||
|
// epitélio pavimentoso visto de cima. Berkshire Community College
|
||||||
|
// Bioscience Image Library (BCCB). CC0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Epithelial_Tissues_Simple_Squamous_Epithelium_(40823230315).jpg
|
||||||
|
// histologia/epitelio-simples-cuboidal.jpg
|
||||||
|
// Túbulos renais em corte transversal, H&E. BCCB. CC0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Epithelial_Tissues_Simple_Cuboidal_Epithelium_(41681552432).jpg
|
||||||
|
// histologia/epitelio-pseudoestratificado.jpg
|
||||||
|
// Traqueia de mamífero em corte transversal, H&E, mostrando o epitélio
|
||||||
|
// pseudoestratificado ciliado. Autor: Magscope. CC BY-SA 4.0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Trachea_(mammal)_histology_cross-section.png
|
||||||
|
// histologia/tecido-conjuntivo-frouxo.jpg
|
||||||
|
// Montagem total (whole mount) de tecido conjuntivo frouxo/areolar,
|
||||||
|
// mostrando a rede de fibras colágenas onduladas. BCCB. CC0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Connective_Tissue_Loose_Aerolar_(41743649782).jpg
|
||||||
|
// histologia/cartilagem-elastica.jpg
|
||||||
|
// Corte transversal de cartilagem elástica de mamífero (coloração
|
||||||
|
// especial pra fibras elásticas). BCCB. CC0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Connective_Tissue_Elastic_Cartilage_(41812803082).jpg
|
||||||
|
// histologia/fibrocartilagem.jpg
|
||||||
|
// Fibrocartilagem de mamífero, tricrômico (colágeno em vermelho,
|
||||||
|
// matriz cartilaginosa em azul). Autora: Magda Yurchishin
|
||||||
|
// (fotografada numa aula de biologia do ensino médio). CC BY 4.0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:Microscopic_Image_of_Mammal_Fibrocartilage_Tissue.jpg
|
||||||
|
// histologia/esfregaco-sanguineo.jpg
|
||||||
|
// Esfregaço sanguíneo periférico corado (Wright), neutrófilo ao centro
|
||||||
|
// entre hemácias e plaquetas. Autor: Ajay Kumar Chaurasiya. CC BY-SA 4.0.
|
||||||
|
// https://commons.wikimedia.org/wiki/File:WBC_(neutrophil)_at_centre,_numerous_erythrocytes_and_platelets_(dot_like_bodies)_in_Wright's_stained_peripheral_blood_smear_(PBS)_microscopy.jpg
|
||||||
|
//
|
||||||
// As posições dos hotspots (x/y em % de cada imagem) ficam em
|
// As posições dos hotspots (x/y em % de cada imagem) ficam em
|
||||||
// frontend/src/data/microscopyHotspots.ts — só apresentação, como no
|
// frontend/src/data/microscopyHotspots.ts — só apresentação, como no
|
||||||
// tegumentar. Este arquivo só gera os DADOS da estrutura (nome, descrição).
|
// tegumentar. Este arquivo só gera os DADOS da estrutura (nome, descrição).
|
||||||
@@ -178,6 +208,86 @@ add("histologia", NERV, "corpo-neuronal-histologia", "Corpo do Neurônio (Soma)"
|
|||||||
"Região do neurônio que contém o núcleo e a maquinaria de síntese proteica; a partir dela partem os prolongamentos (dendritos e axônio) que fazem a célula se comunicar com outras.",
|
"Região do neurônio que contém o núcleo e a maquinaria de síntese proteica; a partir dela partem os prolongamentos (dendritos e axônio) que fazem a célula se comunicar com outras.",
|
||||||
["Tecido Nervoso"]);
|
["Tecido Nervoso"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Epitélio simples pavimentoso
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const EP_PAV = "/histologia/epitelio-simples-pavimentoso.jpg";
|
||||||
|
add("histologia", EP_PAV, "epitelio-simples-pavimentoso", "Epitélio Simples Pavimentoso", "Simple Squamous Epithelium",
|
||||||
|
"Uma única camada de células achatadas (finas, largas), com núcleo também achatado — forma ideal pra difusão rápida de gases e líquidos. Reveste alvéolos pulmonares, cápsula de Bowman no rim, e forma o mesotélio e o endotélio dos vasos sanguíneos.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
add("histologia", EP_PAV, "nucleo-pavimentoso", "Núcleo da Célula Pavimentosa", "Squamous Cell Nucleus",
|
||||||
|
"Núcleo achatado, acompanhando o formato baixo e largo da própria célula — diferente do núcleo arredondado típico de células cúbicas ou colunares.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Epitélio simples cuboidal (túbulos renais)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const EP_CUB = "/histologia/epitelio-simples-cuboidal.jpg";
|
||||||
|
add("histologia", EP_CUB, "epitelio-simples-cuboidal", "Epitélio Simples Cuboidal", "Simple Cuboidal Epithelium",
|
||||||
|
"Uma única camada de células com altura e largura parecidas ('cúbicas'), núcleo arredondado e central. Reveste túbulos renais, folículos da tireoide e ductos de várias glândulas — funções de absorção e secreção.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
add("histologia", EP_CUB, "lumen-tubular-renal", "Luz do Túbulo Renal", "Renal Tubule Lumen",
|
||||||
|
"Espaço central e oco de cada túbulo, por onde passa o filtrado/urina em formação — delimitado ao redor por todo o anel de células cuboidais.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Epitélio pseudoestratificado ciliado (traqueia)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const EP_PSEUDO = "/histologia/epitelio-pseudoestratificado.jpg";
|
||||||
|
add("histologia", EP_PSEUDO, "epitelio-pseudoestratificado", "Epitélio Pseudoestratificado Ciliado", "Pseudostratified Ciliated Epithelium",
|
||||||
|
"Parece ter várias camadas (\"pseudo\"-estratificado), mas é uma ÚNICA camada de células — só que de alturas diferentes, com nem todas alcançando a superfície, o que dá essa falsa impressão. As células que chegam à superfície têm cílios, que varrem o muco (com partículas presas) pra fora das vias respiratórias.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
add("histologia", EP_PSEUDO, "lamina-propria-traqueia", "Lâmina Própria", "Lamina Propria",
|
||||||
|
"Camada de tecido conjuntivo logo abaixo do epitélio, dando sustentação e contendo vasos sanguíneos, glândulas mucosas e fibras elásticas.",
|
||||||
|
["Tecido Epitelial"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Tecido conjuntivo frouxo (areolar)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const CONJ_FROUXO = "/histologia/tecido-conjuntivo-frouxo.jpg";
|
||||||
|
add("histologia", CONJ_FROUXO, "tecido-conjuntivo-frouxo", "Tecido Conjuntivo Frouxo (Areolar)", "Loose (Areolar) Connective Tissue",
|
||||||
|
"Tecido conjuntivo mais comum do corpo: poucas fibras (colágenas e elásticas), dispostas de forma solta e desorganizada numa matriz com bastante substância fundamental — preenche espaços entre órgãos e sob a pele, com boa flexibilidade e vascularização.",
|
||||||
|
["Tecido Conjuntivo"]);
|
||||||
|
add("histologia", CONJ_FROUXO, "fibroblasto-frouxo", "Fibroblasto", "Fibroblast",
|
||||||
|
"Célula mais numerosa do tecido conjuntivo, responsável por produzir e manter as fibras (colágeno, elastina) e a substância fundamental da matriz extracelular.",
|
||||||
|
["Tecido Conjuntivo"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Cartilagem elástica
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const CART_ELAST = "/histologia/cartilagem-elastica.jpg";
|
||||||
|
add("histologia", CART_ELAST, "cartilagem-elastica", "Cartilagem Elástica", "Elastic Cartilage",
|
||||||
|
"Tipo de cartilagem com uma densa rede de fibras ELÁSTICAS na matriz (além do colágeno), o que dá flexibilidade extra, voltando à forma original depois de dobrada — encontrada na orelha externa (pavilhão auricular) e na epiglote.",
|
||||||
|
["Tecido Cartilaginoso"]);
|
||||||
|
add("histologia", CART_ELAST, "condrocito-elastica", "Condrócito", "Chondrocyte",
|
||||||
|
"Célula madura da cartilagem, alojada numa lacuna dentro da matriz que ela mesma produziu e mantém — ao contrário do osso, a cartilagem não tem vasos sanguíneos, então os condrócitos dependem de difusão pra se nutrir.",
|
||||||
|
["Tecido Cartilaginoso"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Fibrocartilagem
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const FIBROCART = "/histologia/fibrocartilagem.jpg";
|
||||||
|
add("histologia", FIBROCART, "fibrocartilagem", "Fibrocartilagem", "Fibrocartilage",
|
||||||
|
"Tipo de cartilagem mais resistente à tração, com grossos feixes de colágeno tipo I visíveis na matriz (ao lado dos condrócitos) — diferente da hialina e da elástica, não tem pericôndrio. Encontrada nos discos intervertebrais e na sínfise púbica, onde precisa suportar bastante carga mecânica.",
|
||||||
|
["Tecido Cartilaginoso"]);
|
||||||
|
add("histologia", FIBROCART, "fibras-colagenas-fibrocartilagem", "Feixes de Colágeno", "Collagen Bundles",
|
||||||
|
"Feixes grossos e densos de colágeno tipo I, organizados em paralelo — dão à fibrocartilagem a resistência à tração que a hialina e a elástica não têm.",
|
||||||
|
["Tecido Cartilaginoso"]);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// HISTOLOGIA — Esfregaço sanguíneo periférico
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const SANGUE = "/histologia/esfregaco-sanguineo.jpg";
|
||||||
|
add("histologia", SANGUE, "neutrofilo-esfregaco", "Neutrófilo", "Neutrophil",
|
||||||
|
"Leucócito mais comum do sangue, com núcleo polimorfonuclear (2 a 5 lóbulos) — primeira linha de defesa celular contra infecções bacterianas, fagocitando os micro-organismos.",
|
||||||
|
["Sangue"]);
|
||||||
|
add("histologia", SANGUE, "hemacias-esfregaco", "Hemácias", "Red Blood Cells",
|
||||||
|
"Células mais numerosas do esfregaço, sem núcleo, com uma área mais clara no centro (o reflexo do formato bicôncavo visto de cima) — transportam oxigênio ligado à hemoglobina.",
|
||||||
|
["Sangue"]);
|
||||||
|
add("histologia", SANGUE, "plaquetas-esfregaco", "Plaquetas", "Platelets",
|
||||||
|
"Fragmentos celulares pequenos (bem menores que uma hemácia), vistos como pontinhos escuros espalhados pelo esfregaço — essenciais pra coagulação sanguínea, se agregando no local de uma lesão vascular.",
|
||||||
|
["Sangue"]);
|
||||||
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
path.resolve(__dirname, "manifest-microscopy.json"),
|
path.resolve(__dirname, "manifest-microscopy.json"),
|
||||||
JSON.stringify({ structures }, null, 2),
|
JSON.stringify({ structures }, null, 2),
|
||||||
|
|||||||
|
After Width: | Height: | Size: 551 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 509 KiB |
|
After Width: | Height: | Size: 702 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 374 KiB |
@@ -10,6 +10,7 @@ import AtlasPage from "./routes/AtlasPage.js";
|
|||||||
const PlanesPage = lazy(() => import("./routes/PlanesPage.js"));
|
const PlanesPage = lazy(() => import("./routes/PlanesPage.js"));
|
||||||
const PhysicalExamPage = lazy(() => import("./routes/PhysicalExamPage.js"));
|
const PhysicalExamPage = lazy(() => import("./routes/PhysicalExamPage.js"));
|
||||||
const CellDivisionPage = lazy(() => import("./routes/CellDivisionPage.js"));
|
const CellDivisionPage = lazy(() => import("./routes/CellDivisionPage.js"));
|
||||||
|
const CellTypesPage = lazy(() => import("./routes/CellTypesPage.js"));
|
||||||
const QuizPage = lazy(() => import("./routes/QuizPage.js"));
|
const QuizPage = lazy(() => import("./routes/QuizPage.js"));
|
||||||
const AdminStatsPage = lazy(() => import("./routes/AdminStatsPage.js"));
|
const AdminStatsPage = lazy(() => import("./routes/AdminStatsPage.js"));
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ export default function App() {
|
|||||||
<Route path="/planos-anatomicos" element={<PlanesPage />} />
|
<Route path="/planos-anatomicos" element={<PlanesPage />} />
|
||||||
<Route path="/exame-fisico" element={<PhysicalExamPage />} />
|
<Route path="/exame-fisico" element={<PhysicalExamPage />} />
|
||||||
<Route path="/divisao-celular" element={<CellDivisionPage />} />
|
<Route path="/divisao-celular" element={<CellDivisionPage />} />
|
||||||
|
<Route path="/tipos-celulares" element={<CellTypesPage />} />
|
||||||
<Route path="/quiz" element={<QuizPage />} />
|
<Route path="/quiz" element={<QuizPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
{/* Fora do Layout: painel interno, sem o menu/nav do atlas. */}
|
{/* Fora do Layout: painel interno, sem o menu/nav do atlas. */}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useMemo, type ReactNode } from "react";
|
import { useMemo } from "react";
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import { CameraControls } from "@react-three/drei";
|
import { CameraControls } from "@react-three/drei";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useAtlasStore } from "../store/useAtlasStore.js";
|
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||||
import type { Structure } from "../lib/api.js";
|
import type { Structure } from "../lib/api.js";
|
||||||
|
import { materialProps, cylinderBetween, Organelle, HIGHLIGHT, BLACK } from "./cell/cellShared.js";
|
||||||
|
|
||||||
// Não existe (ver discussão com o usuário) uma malha 3D real e aberta de
|
// Não existe (ver discussão com o usuário) uma malha 3D real e aberta de
|
||||||
// célula com todas as organelas prontas pra estudo — chegamos a integrar
|
// célula com todas as organelas prontas pra estudo — chegamos a integrar
|
||||||
@@ -14,23 +15,9 @@ import type { Structure } from "../lib/api.js";
|
|||||||
// pedido do usuário, citologia agora é um modelo 3D ILUSTRATIVO — desenhado
|
// pedido do usuário, citologia agora é um modelo 3D ILUSTRATIVO — desenhado
|
||||||
// à mão com primitivas do Three.js, mesmo princípio já usado no boneco
|
// à mão com primitivas do Three.js, mesmo princípio já usado no boneco
|
||||||
// esquemático de Planos Anatômicos (ver Mannequin.tsx) — mais reconhecível
|
// esquemático de Planos Anatômicos (ver Mannequin.tsx) — mais reconhecível
|
||||||
// pra quem está estudando, mesmo não vindo de dado real.
|
// pra quem está estudando, mesmo não vindo de dado real. `materialProps`,
|
||||||
const HIGHLIGHT = new THREE.Color("#22d3ee");
|
// `cylinderBetween` e `Organelle` são compartilhados com
|
||||||
const BLACK = new THREE.Color(0x000000);
|
// routes/CellTypesPage.tsx — ver cell/cellShared.tsx.
|
||||||
|
|
||||||
// Função comum (NÃO hook) de propósito — é chamada dentro de .map() e de
|
|
||||||
// callbacks de render-prop abaixo, onde um hook (useMemo/useState) violaria
|
|
||||||
// as Regras de Hooks do React (número de chamadas por render precisa ser
|
|
||||||
// sempre igual e não pode depender de laços/condicionais).
|
|
||||||
function materialProps(baseColor: string, selected: boolean, opacity = 1) {
|
|
||||||
return {
|
|
||||||
color: baseColor,
|
|
||||||
transparent: opacity < 1,
|
|
||||||
opacity,
|
|
||||||
emissive: selected ? HIGHLIGHT : BLACK,
|
|
||||||
emissiveIntensity: selected ? 0.7 : 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Corte transversal ampliado da bicamada fosfolipídica (cabeça+cauda, as
|
// Corte transversal ampliado da bicamada fosfolipídica (cabeça+cauda, as
|
||||||
// duas camadas com as caudas hidrofóbicas viradas pra dentro, uma pra
|
// duas camadas com as caudas hidrofóbicas viradas pra dentro, uma pra
|
||||||
@@ -92,33 +79,6 @@ function MembraneCrossSection({ selected }: { selected: boolean }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Organelle({
|
|
||||||
slug,
|
|
||||||
bySlug,
|
|
||||||
onSelect,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
slug: string;
|
|
||||||
bySlug: Map<string, Structure>;
|
|
||||||
onSelect: (id: string) => void;
|
|
||||||
children: (isSelected: boolean) => ReactNode;
|
|
||||||
}) {
|
|
||||||
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
|
||||||
const structure = bySlug.get(slug);
|
|
||||||
const isSelected = !!structure && structure.id === selectedStructureId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<group
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (structure) onSelect(structure.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children(isSelected)}
|
|
||||||
</group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pontos de mitocôndrias/lisossomos espalhados pelo citoplasma — fixos
|
// Pontos de mitocôndrias/lisossomos espalhados pelo citoplasma — fixos
|
||||||
// (não aleatórios a cada render) pra não "pular" toda vez que o componente
|
// (não aleatórios a cada render) pra não "pular" toda vez que o componente
|
||||||
// atualiza.
|
// atualiza.
|
||||||
@@ -135,6 +95,17 @@ const RIBOSOME_SPOTS: [number, number, number][] = [
|
|||||||
[-0.5, -0.2, -0.5], [0.55, -0.35, 0.1], [-0.2, 0.6, -0.3], [0.4, 0.1, -0.55],
|
[-0.5, -0.2, -0.5], [0.55, -0.35, 0.1], [-0.2, 0.6, -0.3], [0.4, 0.1, -0.55],
|
||||||
[-0.55, 0.3, 0.25], [0.1, -0.6, -0.25],
|
[-0.55, 0.3, 0.25], [0.1, -0.6, -0.25],
|
||||||
];
|
];
|
||||||
|
const PEROXISSOMO_SPOTS: [number, number, number][] = [
|
||||||
|
[0.45, 0.45, -0.3], [-0.2, -0.4, 0.5], [0.15, -0.55, -0.35], [-0.55, 0.1, 0.4],
|
||||||
|
];
|
||||||
|
// "Raios" do citoesqueleto saindo de perto do núcleo até perto da
|
||||||
|
// membrana, em direções fixas (não aleatórias) — só pra dar a ideia de
|
||||||
|
// rede de filamentos preenchendo o citoplasma, sem tentar reproduzir
|
||||||
|
// microtúbulo/actina/filamento intermediário individualmente.
|
||||||
|
const CYTOSKELETON_RAYS: [number, number, number][] = [
|
||||||
|
[0.7, 0.5, -0.1], [-0.65, 0.4, 0.35], [0.5, -0.55, 0.4], [-0.5, -0.5, -0.45],
|
||||||
|
[0.75, -0.1, -0.5], [-0.75, 0.05, 0.2], [0.15, 0.7, 0.45], [-0.2, -0.75, 0.1],
|
||||||
|
];
|
||||||
|
|
||||||
export function CellDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Structure> }) {
|
export function CellDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Structure> }) {
|
||||||
const toggleSystem = useAtlasStore((s) => s.toggleSystem);
|
const toggleSystem = useAtlasStore((s) => s.toggleSystem);
|
||||||
@@ -272,6 +243,64 @@ export function CellDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Struc
|
|||||||
)}
|
)}
|
||||||
</Organelle>
|
</Organelle>
|
||||||
|
|
||||||
|
{/* Centríolos — par perpendicular perto do núcleo (centrossomo).
|
||||||
|
Mesma posição conceitual de onde os centrossomos aparecem
|
||||||
|
no módulo de Mitose e Meiose: organizam o fuso quando a
|
||||||
|
célula se divide. */}
|
||||||
|
<Organelle slug="celula-centriolos" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
|
{(sel) => (
|
||||||
|
<group position={[0.05, -0.32, 0.22]}>
|
||||||
|
<mesh rotation={[0, 0, 0]}>
|
||||||
|
<cylinderGeometry args={[0.02, 0.02, 0.12, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#f97316", sel)} />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.09, 0, 0.02]} rotation={[0, 0, Math.PI / 2]}>
|
||||||
|
<cylinderGeometry args={[0.02, 0.02, 0.12, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#f97316", sel)} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
{/* Peroxissomos — vesículas pequenas, cor própria (não
|
||||||
|
confundir com os lisossomos amarelos) */}
|
||||||
|
<Organelle slug="celula-peroxissomos" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{PEROXISSOMO_SPOTS.map(([x, y, z], i) => (
|
||||||
|
<mesh key={i} position={[x, y, z]}>
|
||||||
|
<sphereGeometry args={[0.04, 12, 12]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#5eead4", sel, 0.95)} />
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
{/* Citoesqueleto — rede de filamentos finos saindo de perto do
|
||||||
|
núcleo até perto da membrana, preenchendo o citoplasma */}
|
||||||
|
<Organelle slug="celula-citoesqueleto" bySlug={bySlug} onSelect={selectStructure}>
|
||||||
|
{(sel) => {
|
||||||
|
const center = new THREE.Vector3(0.1, 0, 0);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{CYTOSKELETON_RAYS.map((target, i) => {
|
||||||
|
const { position, quaternion, length } = cylinderBetween(
|
||||||
|
center,
|
||||||
|
new THREE.Vector3(...target),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.006, 0.006, length, 5]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#94a3b8", sel, 0.55)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
{/* Membrana plasmática — um RECORTE da casca (não a esfera
|
{/* Membrana plasmática — um RECORTE da casca (não a esfera
|
||||||
inteira) num canto de trás, tipo uma "janela" mostrando
|
inteira) num canto de trás, tipo uma "janela" mostrando
|
||||||
onde ela fica. Uma esfera fechada ao redor de tudo (como
|
onde ela fica. Uma esfera fechada ao redor de tudo (como
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ export function Layout() {
|
|||||||
<NavLink to="/divisao-celular" className={linkClass}>
|
<NavLink to="/divisao-celular" className={linkClass}>
|
||||||
Mitose e Meiose
|
Mitose e Meiose
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink to="/tipos-celulares" className={linkClass}>
|
||||||
|
Tipos Celulares
|
||||||
|
</NavLink>
|
||||||
<NavLink to="/quiz" className={linkClass}>
|
<NavLink to="/quiz" className={linkClass}>
|
||||||
Quiz
|
Quiz
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|||||||
@@ -0,0 +1,368 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import type { Structure } from "../../lib/api.js";
|
||||||
|
import { materialProps, cylinderBetween, Organelle } from "./cellShared.js";
|
||||||
|
|
||||||
|
// Cada função abaixo desenha UM tipo celular — todas ilustrativas (sem
|
||||||
|
// malha 3D real disponível, mesmo princípio de CellDiagram3D.tsx),
|
||||||
|
// pensadas pra destacar o que torna aquele tipo reconhecível/diferente de
|
||||||
|
// uma célula genérica. `bySlug`/`onSelect` seguem o mesmo contrato de
|
||||||
|
// Organelle usado na célula genérica — clicar numa parte abre a descrição
|
||||||
|
// real (ver build-manifest-cell.mjs).
|
||||||
|
|
||||||
|
interface CellTypeProps {
|
||||||
|
bySlug: Map<string, Structure>;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Neurônio
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const DENDRITE_DIRECTIONS: [number, number, number][] = [
|
||||||
|
[-0.55, 0.35, 0.1], [-0.5, -0.1, 0.4], [-0.35, 0.5, -0.3],
|
||||||
|
[-0.6, -0.3, -0.2], [-0.2, 0.55, 0.35], [-0.45, 0.05, -0.5],
|
||||||
|
];
|
||||||
|
const AXON_DIRECTION = new THREE.Vector3(1.4, -0.15, 0.1);
|
||||||
|
const MYELIN_SEGMENTS = [0.25, 0.42, 0.59, 0.76, 0.93]; // posições ao longo do axônio (fração 0-1), com lacunas = nódulos de Ranvier
|
||||||
|
|
||||||
|
export function NeuronModel({ bySlug, onSelect }: CellTypeProps) {
|
||||||
|
const soma = new THREE.Vector3(0, 0, 0);
|
||||||
|
const axonEnd = soma.clone().add(AXON_DIRECTION);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Organelle slug="neuronio-corpo-celular" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
<mesh>
|
||||||
|
<sphereGeometry args={[0.32, 28, 28]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#a78bfa", sel, 0.92)} />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.05, 0.03, 0.06]}>
|
||||||
|
<sphereGeometry args={[0.14, 20, 20]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#6d28d9", sel)} />
|
||||||
|
</mesh>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="neuronio-dendritos" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{DENDRITE_DIRECTIONS.map((dir, i) => {
|
||||||
|
const to = new THREE.Vector3(...dir);
|
||||||
|
const { position, quaternion, length } = cylinderBetween(soma, to);
|
||||||
|
return (
|
||||||
|
<group key={i}>
|
||||||
|
<mesh position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.025, 0.012, length, 6]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#c4b5fd", sel)} />
|
||||||
|
</mesh>
|
||||||
|
{/* sub-ramo curto, só pra sugerir ramificação */}
|
||||||
|
{(() => {
|
||||||
|
const mid = soma.clone().lerp(to, 0.6);
|
||||||
|
const branchTo = mid.clone().add(new THREE.Vector3(dir[2] * 0.2, dir[0] * 0.15, -dir[1] * 0.2));
|
||||||
|
const branch = cylinderBetween(mid, branchTo);
|
||||||
|
return (
|
||||||
|
<mesh position={branch.position} quaternion={branch.quaternion}>
|
||||||
|
<cylinderGeometry args={[0.012, 0.006, branch.length, 5]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#c4b5fd", sel)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="neuronio-axonio" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => {
|
||||||
|
const { position, quaternion, length } = cylinderBetween(soma, axonEnd);
|
||||||
|
return (
|
||||||
|
<mesh position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.035, 0.03, length, 8]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#f472b6", sel)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="neuronio-bainha-mielina" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{MYELIN_SEGMENTS.map((t, i) => {
|
||||||
|
const center = soma.clone().lerp(axonEnd, t);
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={center} rotation={[0, 0, Math.atan2(AXON_DIRECTION.y, AXON_DIRECTION.x)]}>
|
||||||
|
<capsuleGeometry args={[0.065, 0.1, 4, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#fcd34d", sel, 0.85)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="neuronio-terminal-sinaptico" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{[
|
||||||
|
[0.08, 0.06, 0],
|
||||||
|
[0.1, -0.05, 0.06],
|
||||||
|
[0.07, -0.02, -0.07],
|
||||||
|
].map(([dx, dy, dz], i) => (
|
||||||
|
<mesh key={i} position={[axonEnd.x + dx, axonEnd.y + dy, axonEnd.z + dz]}>
|
||||||
|
<sphereGeometry args={[0.055, 14, 14]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#fb7185", sel, 0.95)} />
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Hemácia (eritrócito) — disco bicôncavo de verdade, via perfil revolvido
|
||||||
|
// (mesma técnica usada no torso do Mannequin.tsx: LatheGeometry)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
function useBiconcaveProfile() {
|
||||||
|
// A primeira versão traçava só UMA superfície (centro->borda), o que o
|
||||||
|
// Lathe revolve numa tigela/pires de um lado só, não um disco de
|
||||||
|
// verdade — parecia uma lasca fina vista de qualquer ângulo mais de
|
||||||
|
// lado. Um disco de verdade precisa de um contorno FECHADO no corte
|
||||||
|
// transversal: sobe pela face de CIMA (centro->borda) e desce pela face
|
||||||
|
// de BAIXO (borda->centro), com a borda fina unindo as duas — só assim
|
||||||
|
// o Lathe fecha um sólido com as duas faces côncavas.
|
||||||
|
const halfThicknessByRadius: [radius: number, halfThickness: number][] = [
|
||||||
|
[0.0, 0.015], // centro: bem fino (a concavidade de verdade)
|
||||||
|
[0.1, 0.045],
|
||||||
|
[0.24, 0.1], // anel bem mais grosso que o centro
|
||||||
|
[0.35, 0.065],
|
||||||
|
[0.4, 0.0], // borda fina, as duas faces se encontram aqui
|
||||||
|
];
|
||||||
|
const top = halfThicknessByRadius.map(([r, h]) => new THREE.Vector2(r, h));
|
||||||
|
const bottom = [...halfThicknessByRadius]
|
||||||
|
.reverse()
|
||||||
|
.slice(1) // não repete o ponto da borda (já é o último do "top")
|
||||||
|
.map(([r, h]) => new THREE.Vector2(r, -h));
|
||||||
|
const geo = new THREE.LatheGeometry([...top, ...bottom], 40);
|
||||||
|
geo.computeVertexNormals();
|
||||||
|
return geo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErythrocyteModel({ bySlug, onSelect }: CellTypeProps) {
|
||||||
|
const geometry = useBiconcaveProfile();
|
||||||
|
return (
|
||||||
|
<Organelle slug="hemacia-eritrocito" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<mesh geometry={geometry}>
|
||||||
|
<meshStandardMaterial {...materialProps("#dc2626", sel, 0.96)} />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Célula Muscular Esquelética — fibra longa, núcleos periféricos,
|
||||||
|
// estriações transversais
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const FIBER_LENGTH = 1.7;
|
||||||
|
const FIBER_RADIUS = 0.22;
|
||||||
|
const PERIPHERAL_NUCLEI: [number, number][] = [
|
||||||
|
[-0.55, 0.9], [-0.1, -0.95], [0.4, 0.88], [0.75, -0.85], [-0.8, -0.3],
|
||||||
|
];
|
||||||
|
const STRIATION_COUNT = 14;
|
||||||
|
|
||||||
|
export function MuscleFiberModel({ bySlug, onSelect }: CellTypeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Organelle slug="musculo-fibra" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<mesh rotation={[0, 0, Math.PI / 2]}>
|
||||||
|
<capsuleGeometry args={[FIBER_RADIUS, FIBER_LENGTH - FIBER_RADIUS * 2, 4, 16]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#f87171", sel, 0.9)} />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="musculo-estriacoes" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{Array.from({ length: STRIATION_COUNT }, (_, i) => {
|
||||||
|
const x = -FIBER_LENGTH / 2 + 0.15 + (i / (STRIATION_COUNT - 1)) * (FIBER_LENGTH - 0.3);
|
||||||
|
const dark = i % 2 === 0;
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={[x, 0, 0]} rotation={[0, 0, Math.PI / 2]}>
|
||||||
|
<cylinderGeometry args={[FIBER_RADIUS * 1.001, FIBER_RADIUS * 1.001, 0.045, 20, 1, true]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
{...materialProps(dark ? "#7f1d1d" : "#fca5a5", sel, 0.7)}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="musculo-nucleos-perifericos" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{PERIPHERAL_NUCLEI.map(([x, angleFrac], i) => {
|
||||||
|
const angle = angleFrac * Math.PI;
|
||||||
|
const y = Math.cos(angle) * (FIBER_RADIUS - 0.03);
|
||||||
|
const z = Math.sin(angle) * (FIBER_RADIUS - 0.03);
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={[x, y, z]} scale={[1.4, 0.7, 0.7]}>
|
||||||
|
<sphereGeometry args={[0.07, 14, 14]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#7c2d92", sel)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Espermatozoide — cabeça (+ acrossomo), peça intermediária (+
|
||||||
|
// mitocôndrias em espiral), flagelo
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const HEAD_CENTER = new THREE.Vector3(-0.6, 0, 0);
|
||||||
|
const HEAD_RADIUS = 0.22;
|
||||||
|
// A peça intermediária começa na BORDA da cabeça, não no centro dela —
|
||||||
|
// começar no centro fazia a peça intermediária e as mitocôndrias nascerem
|
||||||
|
// "dentro" da cabeça, virando uma bola confusa de formas sobrepostas.
|
||||||
|
const MIDPIECE_START = new THREE.Vector3(HEAD_CENTER.x + HEAD_RADIUS, 0, 0);
|
||||||
|
const MIDPIECE_END = new THREE.Vector3(0.05, 0, 0);
|
||||||
|
const TAIL_END = new THREE.Vector3(1.3, 0.05, 0);
|
||||||
|
|
||||||
|
export function SpermModel({ bySlug, onSelect }: CellTypeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Organelle slug="espermatozoide-cabeca" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
<mesh position={HEAD_CENTER} scale={[1, 0.62, 0.8]}>
|
||||||
|
<sphereGeometry args={[HEAD_RADIUS, 24, 24]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#38bdf8", sel, 0.95)} />
|
||||||
|
</mesh>
|
||||||
|
{/* acrossomo — capuz na metade anterior (frente) da cabeça */}
|
||||||
|
<mesh position={[HEAD_CENTER.x - 0.1, 0, 0]} scale={[1, 0.6, 0.78]}>
|
||||||
|
<sphereGeometry args={[0.16, 20, 20, 0, Math.PI]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#0ea5e9", sel, 0.95)} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="espermatozoide-peca-intermediaria" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => {
|
||||||
|
const { position, quaternion, length } = cylinderBetween(MIDPIECE_START, MIDPIECE_END);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<mesh position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.045, 0.04, length, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#facc15", sel, 0.5)} />
|
||||||
|
</mesh>
|
||||||
|
{/* mitocôndrias em espiral ao redor */}
|
||||||
|
{Array.from({ length: 8 }, (_, i) => {
|
||||||
|
const t = i / 7;
|
||||||
|
const x = THREE.MathUtils.lerp(MIDPIECE_START.x, MIDPIECE_END.x, t);
|
||||||
|
const angle = t * Math.PI * 4;
|
||||||
|
const y = Math.cos(angle) * 0.055;
|
||||||
|
const z = Math.sin(angle) * 0.055;
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={[x, y, z]} scale={[0.7, 0.45, 0.45]}>
|
||||||
|
<sphereGeometry args={[0.03, 10, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#ef4444", sel)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="espermatozoide-flagelo" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => {
|
||||||
|
const { position, quaternion, length } = cylinderBetween(MIDPIECE_END, TAIL_END);
|
||||||
|
return (
|
||||||
|
<mesh position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.014, 0.004, length, 6]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#7dd3fc", sel)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Organelle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Leucócito (neutrófilo) — núcleo polimorfonuclear + grânulos
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
const NUCLEUS_LOBES: [number, number, number][] = [
|
||||||
|
[-0.12, 0.1, 0], [0.08, 0.14, 0.05], [0.14, -0.08, -0.06], [-0.05, -0.14, 0.08],
|
||||||
|
];
|
||||||
|
const GRANULE_SPOTS: [number, number, number][] = [
|
||||||
|
[0.3, 0.25, 0.1], [-0.3, 0.2, -0.15], [0.25, -0.3, 0.2], [-0.25, -0.25, -0.1],
|
||||||
|
[0.05, 0.35, -0.25], [-0.1, -0.35, 0.2], [0.35, 0.0, -0.1], [-0.35, 0.05, 0.15],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function LeukocyteModel({ bySlug, onSelect }: CellTypeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* citoplasma — só contexto visual, sem clique (mesma razão da
|
||||||
|
membrana da célula genérica: é a casca que envolve tudo) */}
|
||||||
|
<mesh raycast={() => null}>
|
||||||
|
<sphereGeometry args={[0.45, 24, 24]} />
|
||||||
|
<meshStandardMaterial color="#fde68a" transparent opacity={0.14} side={THREE.DoubleSide} depthWrite={false} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
<Organelle slug="leucocito-nucleo-lobulado" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{NUCLEUS_LOBES.map(([x, y, z], i) => (
|
||||||
|
<mesh key={i} position={[x, y, z]}>
|
||||||
|
<sphereGeometry args={[0.13, 18, 18]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#7c3aed", sel, 0.92)} />
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
{/* filamentos finos de cromatina conectando os lóbulos */}
|
||||||
|
{NUCLEUS_LOBES.slice(1).map(([x, y, z], i) => {
|
||||||
|
const from = new THREE.Vector3(...NUCLEUS_LOBES[i]);
|
||||||
|
const to = new THREE.Vector3(x, y, z);
|
||||||
|
const { position, quaternion, length } = cylinderBetween(from, to);
|
||||||
|
return (
|
||||||
|
<mesh key={i} position={position} quaternion={quaternion}>
|
||||||
|
<cylinderGeometry args={[0.02, 0.02, length, 6]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#7c3aed", sel, 0.92)} />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
|
||||||
|
<Organelle slug="leucocito-granulos" bySlug={bySlug} onSelect={onSelect}>
|
||||||
|
{(sel) => (
|
||||||
|
<>
|
||||||
|
{GRANULE_SPOTS.map(([x, y, z], i) => (
|
||||||
|
<mesh key={i} position={[x, y, z]}>
|
||||||
|
<sphereGeometry args={[0.03, 10, 10]} />
|
||||||
|
<meshStandardMaterial {...materialProps("#fb923c", sel, 0.95)} />
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Organelle>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useAtlasStore } from "../../store/useAtlasStore.js";
|
||||||
|
import type { Structure } from "../../lib/api.js";
|
||||||
|
|
||||||
|
// Utilitários compartilhados entre CellDiagram3D.tsx (a célula genérica)
|
||||||
|
// e CellTypesPage.tsx (galeria de tipos celulares especializados) — os
|
||||||
|
// dois são diagramas 3D ILUSTRATIVOS (sem malha real disponível pra isso),
|
||||||
|
// desenhados à mão com primitivas do Three.js, mesmo princípio do boneco
|
||||||
|
// esquemático de Planos Anatômicos (ver Mannequin.tsx).
|
||||||
|
export const HIGHLIGHT = new THREE.Color("#22d3ee");
|
||||||
|
export const BLACK = new THREE.Color(0x000000);
|
||||||
|
|
||||||
|
// Função comum (NÃO hook) de propósito — é chamada dentro de .map() e de
|
||||||
|
// callbacks de render-prop, onde um hook (useMemo/useState) violaria as
|
||||||
|
// Regras de Hooks do React (número de chamadas por render precisa ser
|
||||||
|
// sempre igual e não pode depender de laços/condicionais).
|
||||||
|
export function materialProps(baseColor: string, selected: boolean, opacity = 1) {
|
||||||
|
return {
|
||||||
|
color: baseColor,
|
||||||
|
transparent: opacity < 1,
|
||||||
|
opacity,
|
||||||
|
emissive: selected ? HIGHLIGHT : BLACK,
|
||||||
|
emissiveIntensity: selected ? 0.7 : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posiciona/orienta um cilindro pra ir de verdade do ponto A ao ponto B
|
||||||
|
// (não só "perto" com um ângulo aproximado) — usado em qualquer prolongamento
|
||||||
|
// fino (filamento do citoesqueleto, axônio, flagelo...).
|
||||||
|
export function cylinderBetween(from: THREE.Vector3, to: THREE.Vector3) {
|
||||||
|
const direction = new THREE.Vector3().subVectors(to, from);
|
||||||
|
const length = direction.length();
|
||||||
|
const position = new THREE.Vector3().addVectors(from, to).multiplyScalar(0.5);
|
||||||
|
const quaternion = new THREE.Quaternion().setFromUnitVectors(
|
||||||
|
new THREE.Vector3(0, 1, 0),
|
||||||
|
direction.clone().normalize(),
|
||||||
|
);
|
||||||
|
return { position, quaternion, length };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Organelle({
|
||||||
|
slug,
|
||||||
|
bySlug,
|
||||||
|
onSelect,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
bySlug: Map<string, Structure>;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
children: (isSelected: boolean) => ReactNode;
|
||||||
|
}) {
|
||||||
|
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
||||||
|
const structure = bySlug.get(slug);
|
||||||
|
const isSelected = !!structure && structure.id === selectedStructureId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (structure) onSelect(structure.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children(isSelected)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,4 +18,18 @@ export const MICROSCOPY_CREDITS: Record<string, string> = {
|
|||||||
"OpenStax College, CC BY 4.0 (Wikimedia Commons).",
|
"OpenStax College, CC BY 4.0 (Wikimedia Commons).",
|
||||||
"/histologia/tecido-nervoso.jpg":
|
"/histologia/tecido-nervoso.jpg":
|
||||||
"Medula espinhal de coelho. John Alan Elson, CC BY-SA 4.0 (Wikimedia Commons).",
|
"Medula espinhal de coelho. John Alan Elson, CC BY-SA 4.0 (Wikimedia Commons).",
|
||||||
|
"/histologia/epitelio-simples-pavimentoso.jpg":
|
||||||
|
"Berkshire Community College Bioscience Image Library (BCCB), CC0 (Wikimedia Commons).",
|
||||||
|
"/histologia/epitelio-simples-cuboidal.jpg":
|
||||||
|
"Túbulos renais. BCCB, CC0 (Wikimedia Commons).",
|
||||||
|
"/histologia/epitelio-pseudoestratificado.jpg":
|
||||||
|
"Traqueia de mamífero. Magscope, CC BY-SA 4.0 (Wikimedia Commons).",
|
||||||
|
"/histologia/tecido-conjuntivo-frouxo.jpg":
|
||||||
|
"BCCB, CC0 (Wikimedia Commons).",
|
||||||
|
"/histologia/cartilagem-elastica.jpg":
|
||||||
|
"BCCB, CC0 (Wikimedia Commons).",
|
||||||
|
"/histologia/fibrocartilagem.jpg":
|
||||||
|
"Magda Yurchishin, CC BY 4.0 (Wikimedia Commons).",
|
||||||
|
"/histologia/esfregaco-sanguineo.jpg":
|
||||||
|
"Ajay Kumar Chaurasiya, CC BY-SA 4.0 (Wikimedia Commons).",
|
||||||
};
|
};
|
||||||
@@ -44,4 +44,33 @@ export const MICROSCOPY_HOTSPOTS: Record<string, { x: number; y: number }> = {
|
|||||||
"substancia-branca-histologia": { x: 20, y: 25 },
|
"substancia-branca-histologia": { x: 20, y: 25 },
|
||||||
"substancia-cinzenta-histologia": { x: 65, y: 35 },
|
"substancia-cinzenta-histologia": { x: 65, y: 35 },
|
||||||
"corpo-neuronal-histologia": { x: 66, y: 30 },
|
"corpo-neuronal-histologia": { x: 66, y: 30 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/epitelio-simples-pavimentoso.jpg
|
||||||
|
"epitelio-simples-pavimentoso": { x: 50, y: 50 },
|
||||||
|
"nucleo-pavimentoso": { x: 22, y: 22 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/epitelio-simples-cuboidal.jpg
|
||||||
|
"epitelio-simples-cuboidal": { x: 30, y: 65 },
|
||||||
|
"lumen-tubular-renal": { x: 48, y: 22 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/epitelio-pseudoestratificado.jpg
|
||||||
|
"epitelio-pseudoestratificado": { x: 15, y: 12 },
|
||||||
|
"lamina-propria-traqueia": { x: 40, y: 60 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/tecido-conjuntivo-frouxo.jpg
|
||||||
|
"tecido-conjuntivo-frouxo": { x: 40, y: 55 },
|
||||||
|
"fibroblasto-frouxo": { x: 58, y: 43 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/cartilagem-elastica.jpg
|
||||||
|
"cartilagem-elastica": { x: 50, y: 45 },
|
||||||
|
"condrocito-elastica": { x: 30, y: 43 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/fibrocartilagem.jpg
|
||||||
|
"fibrocartilagem": { x: 35, y: 55 },
|
||||||
|
"fibras-colagenas-fibrocartilagem": { x: 70, y: 15 },
|
||||||
|
|
||||||
|
// Histologia — /histologia/esfregaco-sanguineo.jpg
|
||||||
|
"neutrofilo-esfregaco": { x: 48, y: 42 },
|
||||||
|
"hemacias-esfregaco": { x: 20, y: 20 },
|
||||||
|
"plaquetas-esfregaco": { x: 10, y: 8 },
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { Canvas } from "@react-three/fiber";
|
||||||
|
import { CameraControls } from "@react-three/drei";
|
||||||
|
import { useSystemStructures } from "../hooks/useSystemStructures.js";
|
||||||
|
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||||
|
import {
|
||||||
|
NeuronModel,
|
||||||
|
ErythrocyteModel,
|
||||||
|
MuscleFiberModel,
|
||||||
|
SpermModel,
|
||||||
|
LeukocyteModel,
|
||||||
|
} from "../components/cell/CellTypeModels.js";
|
||||||
|
|
||||||
|
// Galeria de tipos celulares especializados — complementa a célula
|
||||||
|
// genérica (ver CellDiagram3D.tsx, aberta a partir do Atlas) com modelos
|
||||||
|
// 3D ilustrativos de células que têm uma forma/organela bem diferente do
|
||||||
|
// "padrão", pensados pra reconhecer cada tipo pelas suas características.
|
||||||
|
// Mesmo princípio de todo diagrama ilustrativo deste app: sem malha 3D
|
||||||
|
// real disponível pra isso, desenhado à mão com primitivas do Three.js.
|
||||||
|
|
||||||
|
type CellTypeKey = "neuronio" | "hemacia" | "musculo" | "espermatozoide" | "leucocito";
|
||||||
|
|
||||||
|
interface CellTypeDef {
|
||||||
|
key: CellTypeKey;
|
||||||
|
label: string;
|
||||||
|
hint: string;
|
||||||
|
camera: [number, number, number];
|
||||||
|
Model: typeof NeuronModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CELL_TYPES: CellTypeDef[] = [
|
||||||
|
{
|
||||||
|
key: "neuronio",
|
||||||
|
label: "Neurônio",
|
||||||
|
hint: "Transmite impulsos nervosos — repare nos prolongamentos (dendritos/axônio) e na bainha de mielina.",
|
||||||
|
camera: [1.2, 0.8, 1.6],
|
||||||
|
Model: NeuronModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "hemacia",
|
||||||
|
label: "Hemácia (Eritrócito)",
|
||||||
|
hint: "Transporta oxigênio — a única célula sem núcleo, com o formato de disco bicôncavo característico.",
|
||||||
|
camera: [0.35, 0.55, 0.95],
|
||||||
|
Model: ErythrocyteModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "musculo",
|
||||||
|
label: "Célula Muscular Esquelética",
|
||||||
|
hint: "Fibra longa, multinucleada, com estriações — repare nos núcleos na periferia, não no centro.",
|
||||||
|
camera: [1.6, 0.6, 1.6],
|
||||||
|
Model: MuscleFiberModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "espermatozoide",
|
||||||
|
label: "Espermatozoide",
|
||||||
|
hint: "A única célula humana com flagelo verdadeiro — cabeça, peça intermediária e cauda.",
|
||||||
|
camera: [0.3, 0.6, 1.3],
|
||||||
|
Model: SpermModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "leucocito",
|
||||||
|
label: "Leucócito (Neutrófilo)",
|
||||||
|
hint: "Núcleo polimorfonuclear (vários lóbulos) e grânulos citoplasmáticos com enzimas antimicrobianas.",
|
||||||
|
camera: [0.9, 0.7, 0.9],
|
||||||
|
Model: LeukocyteModel,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const VISIBLE_SYSTEMS = new Set(["citologia"]);
|
||||||
|
|
||||||
|
export default function CellTypesPage() {
|
||||||
|
const [typeKey, setTypeKey] = useState<CellTypeKey>("neuronio");
|
||||||
|
const [leftOpen, setLeftOpen] = useState(false);
|
||||||
|
const [rightOpen, setRightOpen] = useState(false);
|
||||||
|
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
||||||
|
const selectStructure = useAtlasStore((s) => s.selectStructure);
|
||||||
|
|
||||||
|
const { bySlug: bySlugAll } = useSystemStructures(VISIBLE_SYSTEMS);
|
||||||
|
const bySlug = useMemo(
|
||||||
|
() => new Map([...bySlugAll.values()].filter((s) => s.regionPath[1] === "Tipos Celulares").map((s) => [s.slug, s])),
|
||||||
|
[bySlugAll],
|
||||||
|
);
|
||||||
|
|
||||||
|
const current = CELL_TYPES.find((c) => c.key === typeKey)!;
|
||||||
|
const selected = selectedStructureId
|
||||||
|
? [...bySlug.values()].find((s) => s.id === selectedStructureId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
function changeType(key: CellTypeKey) {
|
||||||
|
setTypeKey(key);
|
||||||
|
selectStructure(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full relative md:grid md:grid-cols-[240px_1fr_320px]">
|
||||||
|
<aside
|
||||||
|
className={`fixed md:static inset-y-0 left-0 z-30 w-[86vw] max-w-[260px] md:w-auto
|
||||||
|
border-r border-white/10 bg-panel p-4 space-y-1.5 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>
|
||||||
|
<h3 className="text-xs uppercase tracking-wide text-slate-400 mb-2">Tipos Celulares</h3>
|
||||||
|
{CELL_TYPES.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c.key}
|
||||||
|
onClick={() => changeType(c.key)}
|
||||||
|
className={`w-full text-left text-sm px-2.5 py-2 rounded ${
|
||||||
|
c.key === typeKey ? "bg-accent text-white" : "text-slate-300 hover:bg-white/5"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{c.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
☰ Tipos
|
||||||
|
</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 key={typeKey} dpr={[1, 1.5]} camera={{ position: current.camera, fov: 45 }}>
|
||||||
|
<color attach="background" args={["#0b1220"]} />
|
||||||
|
<ambientLight intensity={0.7} />
|
||||||
|
<directionalLight position={[2, 3, 2]} intensity={1} />
|
||||||
|
<directionalLight position={[-2, -1, -2]} intensity={0.3} />
|
||||||
|
<current.Model bySlug={bySlug} onSelect={selectStructure} />
|
||||||
|
<CameraControls makeDefault minDistance={0.3} maxDistance={6} />
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 z-10 bg-panel/90 border border-white/10 rounded-lg px-3 py-2 text-xs text-slate-300 shadow-lg max-w-[90vw] text-center">
|
||||||
|
{current.hint}
|
||||||
|
</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">{current.label}</span>
|
||||||
|
{selected ? (
|
||||||
|
<>
|
||||||
|
<h2 className="text-lg font-semibold text-white leading-snug">{selected.namePt}</h2>
|
||||||
|
<p className="text-sm text-slate-300 leading-relaxed">{selected.description}</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-slate-400">Clique numa parte do modelo 3D pra ver o que ela é.</p>
|
||||||
|
)}
|
||||||
|
<p className="text-[11px] text-slate-500 pt-3 border-t border-white/10 leading-relaxed">
|
||||||
|
Modelo ilustrativo (não derivado de dado real) — proporções e posições relativas são esquemáticas, pensadas pra reconhecer cada parte. Arraste pra girar, role pra dar zoom.
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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/intercostallabels.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/lib/bodyregionbox.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/cell/celltypemodels.tsx","./src/components/cell/cellshared.tsx","./src/components/celldivision/celldivisionscene.tsx","./src/components/viewer/exammarkers.tsx","./src/components/viewer/intercostallabels.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/lib/bodyregionbox.ts","./src/routes/adminstatspage.tsx","./src/routes/atlaspage.tsx","./src/routes/celldivisionpage.tsx","./src/routes/celltypespage.tsx","./src/routes/physicalexampage.tsx","./src/routes/planespage.tsx","./src/routes/quizpage.tsx","./src/store/useatlasstore.ts"],"version":"5.9.3"}
|
||||||