modelo 3d tegumentar
This commit is contained in:
1 parent
e8ef385237
commit
805eb560a0
7 files changed
+3959
-99
No files matched your search
Generated
+3626
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB |
@@ -1,69 +0,0 @@
|
||||
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||
import { SKIN_HOTSPOTS } from "../data/skinHotspots.js";
|
||||
import type { Structure } from "../lib/api.js";
|
||||
|
||||
// O sistema tegumentar não tem malha 3D (Z-Anatomy não cobre pele — ver
|
||||
// nota em data-pipeline/seed/build-manifest.mjs) e duas tentativas de bloco
|
||||
// 3D esquemático não ficaram boas o bastante pra estudo. Em vez disso, é um
|
||||
// diagrama 2D clássico (domínio público, NCI/SEER) com pontos clicáveis —
|
||||
// cada um abre o mesmo StructureInfoPanel usado pelos outros sistemas.
|
||||
export function SkinDiagram({ bySlug }: { bySlug: Map<string, Structure> }) {
|
||||
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
||||
const selectStructure = useAtlasStore((s) => s.selectStructure);
|
||||
const toggleSystem = useAtlasStore((s) => s.toggleSystem);
|
||||
|
||||
const tegumentar = [...bySlug.values()].filter((s) => s.system.slug === "tegumentar");
|
||||
if (tegumentar.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center p-4 bg-ink/70">
|
||||
<div className="relative bg-panel border border-white/10 rounded-xl shadow-2xl p-3 sm:p-4 max-w-2xl w-full">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h2 className="text-sm font-semibold text-white">
|
||||
Corte Transversal da Pele — clique nos pontos pra explorar
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => toggleSystem("tegumentar")}
|
||||
className="text-slate-400 hover:text-white text-sm px-2 py-1"
|
||||
aria-label="Fechar diagrama"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full select-none">
|
||||
<img
|
||||
src="/tegumentar/skin-cross-section.jpg"
|
||||
alt="Corte transversal da pele mostrando epiderme, derme, hipoderme e seus anexos"
|
||||
className="w-full h-auto rounded-lg border border-white/10"
|
||||
draggable={false}
|
||||
/>
|
||||
{tegumentar.map((structure) => {
|
||||
const hotspot = SKIN_HOTSPOTS[structure.slug];
|
||||
if (!hotspot) return null;
|
||||
const isSelected = structure.id === selectedStructureId;
|
||||
return (
|
||||
<button
|
||||
key={structure.id}
|
||||
onClick={() => selectStructure(structure.id)}
|
||||
title={structure.namePt}
|
||||
aria-label={structure.namePt}
|
||||
style={{ left: `${hotspot.x}%`, top: `${hotspot.y}%` }}
|
||||
className={`absolute -translate-x-1/2 -translate-y-1/2 w-4 h-4 rounded-full border-2 transition-all
|
||||
${
|
||||
isSelected
|
||||
? "bg-accent border-white scale-125 shadow-[0_0_0_4px_rgba(79,124,255,0.35)]"
|
||||
: "bg-accent/70 border-white/70 hover:scale-125 hover:bg-accent"
|
||||
}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<p className="text-[11px] text-slate-500 mt-2">
|
||||
Imagem de domínio público (National Cancer Institute / SEER Training Modules), legendas traduzidas para PT-BR.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { CameraControls } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||
import type { Structure } from "../lib/api.js";
|
||||
|
||||
// Histórico (ver data-pipeline/seed/build-manifest.mjs): tentamos duas vezes
|
||||
// um bloco 3D esquemático pro tegumentar e nenhuma ficou boa o bastante,
|
||||
// então virou diagrama 2D com hotspots. A pedido do usuário, tentamos de
|
||||
// novo seguindo o MESMO princípio que funcionou pra citologia
|
||||
// (CellDiagram3D.tsx): em vez de tentar esculpir uma "pele" realista, é um
|
||||
// bloco de tecido em corte — como a ilustração clássica de livro-texto —
|
||||
// com cada camada/anexo como uma forma geométrica simples e reconhecível,
|
||||
// clicável, usando as MESMAS 20 estruturas (nomes/descrições reais) que já
|
||||
// existiam no diagrama 2D. Ilustrativo, não derivado de dado real.
|
||||
const HIGHLIGHT = new THREE.Color("#22d3ee");
|
||||
const BLACK = new THREE.Color(0x000000);
|
||||
|
||||
// Função comum (NÃO hook) — chamada dentro de .map()/render-prop, onde um
|
||||
// hook violaria as Regras de Hooks do React (ver mesmo padrão em
|
||||
// CellDiagram3D.tsx).
|
||||
function materialProps(baseColor: string, selected: boolean, opacity = 1) {
|
||||
return {
|
||||
color: baseColor,
|
||||
transparent: opacity < 1,
|
||||
opacity,
|
||||
emissive: selected ? HIGHLIGHT : BLACK,
|
||||
emissiveIntensity: selected ? 0.7 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function Part({
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
// Bloco de tecido: 2 x 2 (largura x profundidade), cortado ao meio pra
|
||||
// expor as camadas de lado. Eixo Y é a profundidade da pele (0 = topo).
|
||||
const BLOCK = 1.0; // meia-largura/profundidade do bloco (de -BLOCK a BLOCK)
|
||||
const EPIDERME_TOP = 0;
|
||||
const EPIDERME_BOT = -0.12;
|
||||
const DERME_BOT = -0.55;
|
||||
const HIPODERME_BOT = -1.0;
|
||||
|
||||
// Vasos/nervos como tubos ao longo de uma curva — evita usar cylinderGeometry
|
||||
// reto demais pra estruturas que na pele são ramificadas/sinuosas.
|
||||
function tube(points: [number, number, number][], radius: number) {
|
||||
const curve = new THREE.CatmullRomCurve3(points.map(([x, y, z]) => new THREE.Vector3(x, y, z)));
|
||||
return new THREE.TubeGeometry(curve, 24, radius, 8, false);
|
||||
}
|
||||
|
||||
// Posições fixas (não aleatórias) dos glóbulos de gordura na hipoderme.
|
||||
const FAT_CELLS: [number, number, number, number][] = [
|
||||
[-0.5, -0.72, -0.4, 0.09], [-0.2, -0.85, -0.55, 0.11], [0.15, -0.7, -0.35, 0.08],
|
||||
[0.45, -0.88, -0.5, 0.1], [-0.55, -0.9, -0.15, 0.08], [0.1, -0.95, -0.6, 0.09],
|
||||
[-0.15, -0.65, -0.6, 0.07], [0.5, -0.65, -0.25, 0.08],
|
||||
];
|
||||
|
||||
export function SkinDiagram3D({ bySlug: bySlugAll }: { bySlug: Map<string, Structure> }) {
|
||||
const toggleSystem = useAtlasStore((s) => s.toggleSystem);
|
||||
const selectStructure = useAtlasStore((s) => s.selectStructure);
|
||||
|
||||
const bySlug = useMemo(
|
||||
() => new Map([...bySlugAll.values()].filter((s) => s.system.slug === "tegumentar").map((s) => [s.slug, s])),
|
||||
[bySlugAll],
|
||||
);
|
||||
|
||||
const vesselTube = useMemo(() => tube([[0.75, -0.35, -0.75], [0.55, -0.5, -0.3], [0.3, -0.6, 0.1], [0.05, -0.65, 0.4]], 0.03), []);
|
||||
const arteryTube = useMemo(() => tube([[0.85, -1.0, -0.85], [0.7, -0.7, -0.6], [0.55, -0.5, -0.3]], 0.035), []);
|
||||
const veinTube = useMemo(() => tube([[0.9, -1.0, -0.6], [0.75, -0.68, -0.35], [0.55, -0.48, -0.15]], 0.035), []);
|
||||
const nerveFiberTube = useMemo(() => tube([[-0.85, -0.95, 0.6], [-0.6, -0.6, 0.3], [-0.3, -0.3, 0.1], [-0.1, -0.15, 0]], 0.02), []);
|
||||
|
||||
if (bySlug.size === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center p-4 bg-ink/70">
|
||||
<div className="relative bg-panel border border-white/10 rounded-xl shadow-2xl p-3 sm:p-4 max-w-2xl w-full">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h2 className="text-sm font-semibold text-white">
|
||||
Sistema Tegumentar (Pele) — modelo ilustrativo, gire e clique nas estruturas
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => toggleSystem("tegumentar")}
|
||||
className="text-slate-400 hover:text-white text-sm px-2 py-1"
|
||||
aria-label="Fechar diagrama"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="w-full aspect-square rounded-lg border border-white/10 overflow-hidden bg-[#0b1220]">
|
||||
<Canvas dpr={[1, 1.5]} camera={{ position: [2.6, 1.7, 2.6], fov: 42 }}>
|
||||
<color attach="background" args={["#0b1220"]} />
|
||||
<ambientLight intensity={0.75} />
|
||||
<directionalLight position={[3, 4, 3]} intensity={1} />
|
||||
<directionalLight position={[-3, -1, -2]} intensity={0.3} />
|
||||
|
||||
{/* Hipoderme — camada mais funda, com glóbulos de gordura */}
|
||||
<Part slug="hipoderme" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0, (DERME_BOT + HIPODERME_BOT) / 2, 0]}>
|
||||
<boxGeometry args={[BLOCK * 2, DERME_BOT - HIPODERME_BOT, BLOCK * 2]} />
|
||||
<meshStandardMaterial {...materialProps("#fde68a", sel, 0.9)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Derme — camada intermediária */}
|
||||
<Part slug="derme" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0, (EPIDERME_BOT + DERME_BOT) / 2, 0]}>
|
||||
<boxGeometry args={[BLOCK * 2, EPIDERME_BOT - DERME_BOT, BLOCK * 2]} />
|
||||
<meshStandardMaterial {...materialProps("#f4a896", sel, 0.92)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Papilas dérmicas — pequenas ondulações no topo da derme, contra a epiderme */}
|
||||
<Part slug="papila-dermica" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<>
|
||||
{[-0.6, -0.2, 0.2, 0.6].map((x, i) => (
|
||||
<mesh key={i} position={[x, EPIDERME_BOT + 0.02, 0.5]}>
|
||||
<coneGeometry args={[0.06, 0.08, 12]} />
|
||||
<meshStandardMaterial {...materialProps("#e0806a", sel)} />
|
||||
</mesh>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Epiderme — camada mais fina, no topo, dividida em 3 sub-camadas visíveis */}
|
||||
<Part slug="estrato-germinativo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0, EPIDERME_BOT + 0.02, 0]}>
|
||||
<boxGeometry args={[BLOCK * 2, 0.04, BLOCK * 2]} />
|
||||
<meshStandardMaterial {...materialProps("#c98a5e", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="camada-pigmentar" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0, EPIDERME_BOT + 0.07, 0]}>
|
||||
<boxGeometry args={[BLOCK * 2, 0.06, BLOCK * 2]} />
|
||||
<meshStandardMaterial {...materialProps("#a8703f", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="estrato-corneo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0, EPIDERME_TOP - 0.01, 0]}>
|
||||
<boxGeometry args={[BLOCK * 2, 0.02, BLOCK * 2]} />
|
||||
<meshStandardMaterial {...materialProps("#f2d9b8", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Complexo pilossebáceo — pelo + folículo + papila + eretor + sebácea,
|
||||
cortado na metade frontal do bloco pra ficar visível por dentro. */}
|
||||
<Part slug="foliculo-piloso" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.35, (EPIDERME_TOP + HIPODERME_BOT + 0.2) / 2, 0.45]} rotation={[0, 0, 0.12]}>
|
||||
<cylinderGeometry args={[0.07, 0.05, 1.05, 16, 1, true]} />
|
||||
<meshStandardMaterial {...materialProps("#d8b48c", sel, 0.85)} side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="papila-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.41, HIPODERME_BOT + 0.22, 0.45]}>
|
||||
<sphereGeometry args={[0.06, 16, 16]} />
|
||||
<meshStandardMaterial {...materialProps("#dc2626", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.3, EPIDERME_TOP + 0.4, 0.45]} rotation={[0, 0, 0.12]}>
|
||||
<cylinderGeometry args={[0.012, 0.022, 0.75, 8]} />
|
||||
<meshStandardMaterial {...materialProps("#3f2a1d", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="musculo-eretor-do-pelo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.2, EPIDERME_BOT - 0.15, 0.3]} rotation={[0, 0, -0.55]}>
|
||||
<boxGeometry args={[0.025, 0.32, 0.05]} />
|
||||
<meshStandardMaterial {...materialProps("#b91c1c", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="glandula-sebacea" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.14, EPIDERME_BOT - 0.08, 0.38]}>
|
||||
<sphereGeometry args={[0.08, 16, 16]} />
|
||||
<meshStandardMaterial {...materialProps("#fbbf24", sel, 0.95)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Glândula sudorípara — tubo enovelado (coil) na derme profunda/hipoderme,
|
||||
com ducto reto até a superfície e poro no topo. */}
|
||||
<Part slug="glandula-sudoripara" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<group position={[0.4, HIPODERME_BOT + 0.3, -0.1]}>
|
||||
<mesh>
|
||||
<torusKnotGeometry args={[0.11, 0.03, 64, 8, 2, 3]} />
|
||||
<meshStandardMaterial {...materialProps("#38bdf8", sel, 0.95)} />
|
||||
</mesh>
|
||||
</group>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="poro-sudoriparo" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[0.4, EPIDERME_TOP + 0.005, -0.1]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||
<ringGeometry args={[0.02, 0.035, 16]} />
|
||||
<meshStandardMaterial {...materialProps("#38bdf8", sel)} side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Ducto reto ligando a glândula sudorípara ao poro — parte visual da
|
||||
glândula, sem estrutura própria no banco (usa o mesmo slug). */}
|
||||
<mesh position={[0.4, (HIPODERME_BOT + 0.3 + EPIDERME_TOP) / 2, -0.1]} raycast={() => null}>
|
||||
<cylinderGeometry args={[0.025, 0.025, EPIDERME_TOP - (HIPODERME_BOT + 0.3), 10]} />
|
||||
<meshStandardMaterial color="#38bdf8" transparent opacity={0.5} />
|
||||
</mesh>
|
||||
|
||||
{/* Corpúsculo de Pacini — receptor em camadas concêntricas ("cebola"), fundo da hipoderme */}
|
||||
<Part slug="corpusculo-de-pacini" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<group position={[0.15, HIPODERME_BOT + 0.18, 0.35]} rotation={[0.3, 0.4, 0]}>
|
||||
{[0.16, 0.12, 0.08, 0.04].map((r, i) => (
|
||||
<mesh key={i} scale={[1, 0.55, 1]}>
|
||||
<sphereGeometry args={[r, 16, 12]} />
|
||||
<meshStandardMaterial
|
||||
{...materialProps("#fef3c7", sel, 0.5)}
|
||||
wireframe={i % 2 === 0}
|
||||
/>
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Terminação nervosa de tato — pequeno disco na junção derme-epiderme */}
|
||||
<Part slug="terminacao-nervosa-tato" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh position={[-0.55, EPIDERME_BOT - 0.02, -0.2]} rotation={[Math.PI / 2, 0, 0]}>
|
||||
<capsuleGeometry args={[0.045, 0.06, 4, 8]} />
|
||||
<meshStandardMaterial {...materialProps("#fef08a", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Fibra nervosa — filamento sinuoso subindo da hipoderme até a derme */}
|
||||
<Part slug="fibra-nervosa" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh geometry={nerveFiberTube}>
|
||||
<meshStandardMaterial {...materialProps("#facc15", sel)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Vascularização — rede de vasos + ramo arterial + ramo venoso, tubos sinuosos */}
|
||||
<Part slug="vasos-sanguineos-linfaticos" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh geometry={vesselTube}>
|
||||
<meshStandardMaterial {...materialProps("#22c55e", sel, 0.95)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="arteria" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh geometry={arteryTube}>
|
||||
<meshStandardMaterial {...materialProps("#dc2626", sel, 0.95)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
<Part slug="veia" bySlug={bySlug} onSelect={selectStructure}>
|
||||
{(sel) => (
|
||||
<mesh geometry={veinTube}>
|
||||
<meshStandardMaterial {...materialProps("#2563eb", sel, 0.95)} />
|
||||
</mesh>
|
||||
)}
|
||||
</Part>
|
||||
|
||||
{/* Glóbulos de gordura na hipoderme — só decorativos (compõem a textura
|
||||
da camada, sem estrutura própria no banco). */}
|
||||
{FAT_CELLS.map(([x, y, z, r], i) => (
|
||||
<mesh key={i} position={[x, y, z]} raycast={() => null}>
|
||||
<sphereGeometry args={[r, 12, 12]} />
|
||||
<meshStandardMaterial color="#fef9c3" transparent opacity={0.5} />
|
||||
</mesh>
|
||||
))}
|
||||
|
||||
<CameraControls makeDefault minDistance={0.8} maxDistance={8} />
|
||||
</Canvas>
|
||||
</div>
|
||||
|
||||
<p className="text-[11px] text-slate-500 mt-2">
|
||||
Modelo ilustrativo (não derivado de dado real) — bloco de pele em corte, proporções esquemáticas pensadas pra reconhecer cada camada e anexo.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Posição (em % da largura/altura da imagem) de cada marcador clicável
|
||||
// sobre frontend/public/tegumentar/skin-cross-section.jpg — medidas
|
||||
// diretamente sobre a imagem (domínio público, NCI/SEER Training Modules).
|
||||
// Isso é só apresentação (onde desenhar o ponto em cima da figura); os
|
||||
// dados da estrutura em si (nome, descrição) vêm do banco, como qualquer
|
||||
// outra estrutura do atlas — ver data-pipeline/seed/build-manifest.mjs.
|
||||
export const SKIN_HOTSPOTS: Record<string, { x: number; y: number }> = {
|
||||
"estrato-corneo": { x: 37.9, y: 15.0 },
|
||||
"camada-pigmentar": { x: 37.5, y: 18.6 },
|
||||
"papila-dermica": { x: 48.3, y: 27.2 },
|
||||
epiderme: { x: 75.0, y: 32.2 },
|
||||
"terminacao-nervosa-tato": { x: 74.6, y: 38.7 },
|
||||
derme: { x: 75.4, y: 45.8 },
|
||||
"glandula-sebacea": { x: 69.2, y: 47.3 },
|
||||
"foliculo-piloso": { x: 65.8, y: 53.7 },
|
||||
"papila-do-pelo": { x: 65.8, y: 58.7 },
|
||||
hipoderme: { x: 75.4, y: 65.9 },
|
||||
"estrato-germinativo": { x: 37.9, y: 33.7 },
|
||||
"musculo-eretor-do-pelo": { x: 42.1, y: 43.0 },
|
||||
"fibra-nervosa": { x: 38.8, y: 49.4 },
|
||||
"vasos-sanguineos-linfaticos": { x: 35.0, y: 78.1 },
|
||||
"glandula-sudoripara": { x: 47.1, y: 80.2 },
|
||||
"corpusculo-de-pacini": { x: 51.3, y: 71.6 },
|
||||
arteria: { x: 70.4, y: 78.1 },
|
||||
veia: { x: 75.0, y: 80.9 },
|
||||
pelo: { x: 65.8, y: 9.3 },
|
||||
"poro-sudoriparo": { x: 69.2, y: 27.9 },
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import { ClippingControls } from "../components/ClippingControls.js";
|
||||
import { SearchNav } from "../components/SearchNav.js";
|
||||
import { StructureInfoPanel } from "../components/StructureInfoPanel.js";
|
||||
import { QuickRegionNav } from "../components/QuickRegionNav.js";
|
||||
import { SkinDiagram } from "../components/SkinDiagram.js";
|
||||
import { SkinDiagram3D } from "../components/SkinDiagram3D.js";
|
||||
import { MicroscopyDiagram } from "../components/MicroscopyDiagram.js";
|
||||
import { CellDiagram3D } from "../components/CellDiagram3D.js";
|
||||
import { trackVisit } from "../lib/analytics.js";
|
||||
@@ -73,7 +73,7 @@ export default function AtlasPage() {
|
||||
<div className="relative h-full min-h-0">
|
||||
<Scene bySlug={bySlug} />
|
||||
<QuickRegionNav />
|
||||
<SkinDiagram bySlug={bySlug} />
|
||||
<SkinDiagram3D bySlug={bySlug} />
|
||||
<MicroscopyDiagram bySlug={bySlug} systemSlug="histologia" title="Histologia (Tecidos)" />
|
||||
<CellDiagram3D bySlug={bySlug} />
|
||||
|
||||
|
||||
@@ -0,0 +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/viewer/exammarkers.tsx","./src/components/viewer/mannequin.tsx","./src/components/viewer/scene.tsx","./src/components/viewer/systemmodel.tsx","./src/data/microscopycredits.ts","./src/data/microscopyhotspots.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/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