/* Brain Project - app composition, state & scene wiring */

const CAT_ORDER = window.BRAIN.depth; // outer -> inner peel order
// Warm, muted tissue tones based on fixed neonatal dissection specimens.
// White matter is deliberately only a little lighter than cortical grey.
const TISSUE_COLORS = { cortex: '#A9827B', white_matter: '#B99991', deep_grey: '#747A82' };
const SHORT = {
  meninges_dura: 'Dura & falx', veins_sinuses: 'Sinuses', arteries: 'Arteries', cortex: 'Cortex',
  white_matter: 'White matter', deep_grey: 'Deep grey', diencephalon: 'Diencephalon',
  ventricles: 'Ventricles', brainstem: 'Brainstem', cerebellum: 'Cerebellum', cranial_nerves: 'Nerves',
};

const PRESETS = [
  { id: 'whole',  label: 'Newborn overview', color: 'var(--c-cortex)',         on: ['cortex','deep_grey','diencephalon','ventricles','cerebellum','brainstem'], cortex: 1, focus: null },
  { id: 'midsag', label: 'Mid-sagittal', color: 'var(--c-white_matter)', on: ['cortex','white_matter','diencephalon','ventricles','brainstem','cerebellum','arteries','veins_sinuses'], cortex: 1, focus: null, view: 'sagittal', slice: { axis: 0, value: 0 },
    subset: {
      white_matter: ['Corpus callosum','Fornix','Anterior commissure'],
      ventricles: ['Septum pellucidum','Third ventricle','Fourth ventricle'],
      brainstem: ['Aqueduct of midbrain','Midbrain','Pons','Medulla oblongata'],
      arteries: ['Anterior communicating artery','Anterior cerebral artery','Pericallosal artery','Basilar artery','Posterior cerebral artery'],
      veins_sinuses: ['Superior sagittal sinus','Inferior sagittal sinus','Straight sinus'],
    } },
  { id: 'parasag', label: 'Para-sagittal', color: 'var(--c-ventricles)', on: ['cortex','white_matter','deep_grey','diencephalon','ventricles','brainstem','cerebellum'], cortex: 1, focus: null, view: 'sagittal', slice: { axis: 0, value: 24 } },
  { id: 'coronal', label: 'Neonatal coronal', color: 'var(--c-deep_grey)', on: ['cortex','white_matter','deep_grey','diencephalon','ventricles','brainstem','cerebellum'], cortex: 1, focus: null, view: 'coronal', slice: { axis: 2, value: 0 } },
  { id: 'monro-cor', label: 'Foramen of Monro', color: 'var(--c-ventricles)', on: ['cortex','white_matter','deep_grey','diencephalon','ventricles'], cortex: .72, focus: null, view: 'coronal', slice: { axis: 2, targets: ['Third ventricle','Lateral ventricle','Septum pellucidum'] } },
  { id: 'axial', label: 'Neonatal axial', color: 'var(--c-diencephalon)', on: ['cortex','white_matter','deep_grey','diencephalon','ventricles','brainstem','cerebellum'], cortex: 1, focus: null, view: 'axial', slice: { axis: 1, value: 0 } },
  { id: 'cc-sag', label: 'Callosum sagittal', color: 'var(--c-white_matter)', on: ['cortex','white_matter','ventricles','diencephalon'], cortex: .34, focus: null, view: 'sagittal', slice: { axis: 0, target: 'Corpus callosum' }, subset: { white_matter: ['Corpus callosum'] } },
  { id: 'cc-cor', label: 'Callosum coronal', color: 'var(--c-white_matter)', on: ['cortex','white_matter','ventricles','deep_grey'], cortex: .34, focus: null, view: 'coronal', slice: { axis: 2, target: 'Corpus callosum' }, subset: { white_matter: ['Corpus callosum'] } },
  { id: 'cc-axi', label: 'Callosum axial', color: 'var(--c-white_matter)', on: ['cortex','white_matter','ventricles','deep_grey'], cortex: .34, focus: null, view: 'axial', slice: { axis: 1, target: 'Corpus callosum' }, subset: { white_matter: ['Corpus callosum'] } },
  { id: 'vasc',   label: 'Vasculature',      color: 'var(--c-arteries)',       on: ['arteries','veins_sinuses'],        cortex: 0.12, focus: 'arteries' },
  { id: 'willis', label: 'Circle of Willis', color: 'var(--c-arteries)',       on: ['arteries'],                        cortex: 0.08, focus: 'arteries',
    subset: { arteries: ['Anterior cerebral artery', 'Anterior communicating artery', 'Internal carotid artery', 'Posterior communicating artery', 'Posterior cerebral artery', 'Basilar artery'] } },
  { id: 'vent',   label: 'Ventricles',       color: 'var(--c-ventricles)',     on: ['ventricles'],                      cortex: 0.10, focus: 'ventricles' },
  { id: 'limbic', label: 'Limbic system',    color: 'var(--c-deep_grey)',      on: ['deep_grey','white_matter','diencephalon'], cortex: 0.12, focus: 'deep_grey',
    // Only the true limbic structures - excludes corpus callosum, basal ganglia, thalamus, geniculate/optic relays, etc.
    subset: {
      deep_grey:    ['Lateral nucleus', 'Basolateral complex', 'Central nucleus', 'Corticomedial group', 'Septal nuclei'],
      white_matter: ['Fornix', 'Hippocampal commissure', 'Stria terminalis', 'Anterior commissure'],
      diencephalon: ['Mamillary body', 'Preoptic hypothalamus', 'Anterior hypothalamus', 'Tuberal hypothalamus', 'Lateral hypothalamus', 'Posterior hypothalamus', 'Habenula', 'Stria medullaris thalami'],
    } },
  { id: 'deep',   label: 'Deep grey',        color: 'var(--c-deep_grey)',      on: ['deep_grey','diencephalon'],   cortex: 0.12, focus: 'deep_grey' },
  { id: 'bgnuc',  label: 'Basal ganglia',    color: 'var(--c-deep_grey)',      on: ['deep_grey','brainstem'],           cortex: 0.10, focus: 'deep_grey',
    // Caudate/putamen/accumbens + the atlas-derived pallidal & subthalamic nuclei (GPe/GPi, STN, SN) and the brainstem red nucleus.
    subset: {
      deep_grey: ['Caudate nucleus', 'Putamen', 'Nucleus accumbens', 'Globus pallidus external', 'Globus pallidus internal', 'Subthalamic nucleus', 'Substantia nigra'],
      brainstem: ['Red nucleus'],
    } },
  { id: 'thalnuc', label: 'Thalamic nuclei', color: 'var(--c-diencephalon)',   on: ['diencephalon'],                    cortex: 0.10, focus: 'diencephalon',
    subset: { diencephalon: ['Pulvinar', 'Anterior nuclei of thalamus', 'Mediodorsal nucleus', 'Ventral laterodorsal nucleus', 'Ventral lateroventral nucleus', 'Ventral anterior nucleus', 'Intralaminar and lateral posterior nuclei', 'Lateral geniculate body', 'Medial geniculate body'] } },
  { id: 'nerves', label: 'Cranial nerves',   color: 'var(--c-cranial_nerves)', on: ['cranial_nerves','brainstem'],      cortex: 0.12, focus: 'cranial_nerves' },
  { id: 'dura',   label: 'Meninges & dura',  color: 'var(--c-meninges_dura)',  on: ['meninges_dura','veins_sinuses'],   cortex: 0.30, focus: null },
];

const NEONATAL_LANDMARKS = {
  midsag: {
    title: 'Mid-sagittal landmarks',
    labels: ['Corpus callosum','Fornix','Septum pellucidum','Third ventricle','Aqueduct of midbrain','Fourth ventricle','Midbrain','Pons','Medulla oblongata','Mamillary body','Pineal gland','Optic chiasm','Pericallosal artery','Basilar artery','Superior sagittal sinus','Inferior sagittal sinus','Straight sinus'],
    missing: ['Cavum septi pellucidi as a separate cavity','Interthalamic adhesion','Vein of Galen','Confluence of sinuses']
  },
  parasag: {
    title: 'Para-sagittal landmarks',
    labels: ['Lateral ventricle','Choroid plexus','Caudate nucleus','Putamen','Globus pallidus external','Globus pallidus internal','Pulvinar','Hippocampus','Parahippocampal gyrus'],
    missing: ['Internal capsule as a discrete structure','Named frontal, body, occipital, and temporal ventricular horns']
  },
  coronal: {
    title: 'Coronal landmarks',
    labels: ['Lateral ventricle','Third ventricle','Corpus callosum','Septum pellucidum','Caudate nucleus','Putamen','Globus pallidus external','Globus pallidus internal','Pulvinar','Pons','Choroid plexus'],
    missing: ['Anterior and posterior limbs of internal capsule','Interthalamic adhesion','Lentiform nucleus as a grouped structure']
  },
  axial: {
    title: 'Axial landmarks',
    labels: ['Lateral ventricle','Third ventricle','Fourth ventricle','Choroid plexus','Caudate nucleus','Putamen','Globus pallidus external','Globus pallidus internal','Pulvinar','Optic chiasm','Midbrain','Pons','Hippocampus'],
    missing: ['Internal capsule as a discrete structure','Foramina of Monro, Luschka, and Magendie','Dentate nucleus','Ventricular horns as separate meshes']
  },
  vent: {
    title: 'Ventricular-system landmarks',
    labels: ['Lateral ventricle','Septum pellucidum','Choroid plexus','Third ventricle','Aqueduct of midbrain','Fourth ventricle'],
    missing: ['Foramen of Monro','Foramina of Luschka','Foramen of Magendie','Separate ventricular horns']
  }
};

// Shuffle palettes - 12 subsystems incl. white-matter tracts. These are deliberately
// far apart from each other (cortex tone, saturation and overall mood all shift) so a
// single tap on "Palette" is an unmistakable change, not a subtle nudge. The page default
// lives in data.js (window.BRAIN.palette); a refresh always returns to it.
const PALETTES = [
  // Jewel - saturated, gem-bright on a warm-cream cortex
  { name: 'Jewel',    colors: { cortex:'#E7DEC9', white_matter:'#D7DDE8', deep_grey:'#B57BE0', diencephalon:'#7E8CF2', brainstem:'#E8B24A', cerebellum:'#F0894E', ventricles:'#3FC8D6', arteries:'#F05068', veins_sinuses:'#5078E8', cranial_nerves:'#D9D24A', meninges_dura:'#CC63CC', tracts:'#46C2B0' } },
  // Ice - cool monochrome blues/teals, cortex a pale frost, one warm rescue (arteries)
  { name: 'Ice',      colors: { cortex:'#D6E2EC', white_matter:'#C4D4E2', deep_grey:'#6E86C8', diencephalon:'#4F9AD6', brainstem:'#3FB0C4', cerebellum:'#56C8C0', ventricles:'#7FE0E4', arteries:'#F2756A', veins_sinuses:'#3E72C8', cranial_nerves:'#9ED0E0', meninges_dura:'#8C9ED8', tracts:'#34C0D8' } },
  // Ember - earthy, warm clay & amber; cool structures pulled toward terracotta
  { name: 'Ember',    colors: { cortex:'#E8CDA8', white_matter:'#D8C4B0', deep_grey:'#C2683E', diencephalon:'#B8884A', brainstem:'#E0982E', cerebellum:'#E8743A', ventricles:'#C9A86A', arteries:'#E03C3C', veins_sinuses:'#8C5A86', cranial_nerves:'#F0C44A', meninges_dura:'#A85C4A', tracts:'#C98A3E' } },
  // Neon - electric, fully saturated against a cool-slate cortex
  { name: 'Neon',     colors: { cortex:'#C7D2DE', white_matter:'#AEBCCE', deep_grey:'#B14DFF', diencephalon:'#3D8BFF', brainstem:'#FFD23D', cerebellum:'#FF7A2E', ventricles:'#1EE0E0', arteries:'#FF2D6B', veins_sinuses:'#3A5DFF', cranial_nerves:'#EAFF3D', meninges_dura:'#FF4DE0', tracts:'#2DFFB0' } },
  // Bloom - soft pastels, gentle and desaturated, lavender-tinted cortex
  { name: 'Bloom',    colors: { cortex:'#EADFE8', white_matter:'#DCD6E6', deep_grey:'#B79CDE', diencephalon:'#9CB4E6', brainstem:'#E6C68A', cerebellum:'#E8A88C', ventricles:'#9CD8D2', arteries:'#E892A2', veins_sinuses:'#9CAEE0', cranial_nerves:'#D8D08C', meninges_dura:'#D29ECE', tracts:'#92CFC4' } },
  // Forest - greens, teals and ambers; mossy cortex, earthy and organic
  { name: 'Forest',   colors: { cortex:'#D8DEC2', white_matter:'#C6D2BE', deep_grey:'#7A9E6E', diencephalon:'#4F9E86', brainstem:'#D2A23C', cerebellum:'#E0883C', ventricles:'#46C2A0', arteries:'#D8543C', veins_sinuses:'#3E7E8C', cranial_nerves:'#C6C23C', meninges_dura:'#8E9E5C', tracts:'#5CB87E' } },
  // Mono - near-greyscale with a single bold accent per cool/warm; stark and editorial
  { name: 'Mono',     colors: { cortex:'#DEDEDE', white_matter:'#C8C8C8', deep_grey:'#8E8E96', diencephalon:'#9A9AA2', brainstem:'#B0AEA6', cerebellum:'#A6A6AA', ventricles:'#7FC2C8', arteries:'#E64A5C', veins_sinuses:'#5A78C8', cranial_nerves:'#D8C84A', meninges_dura:'#9A8EA2', tracts:'#6ABFB8' } },
  // Spectrum - the full wheel, maximally varied hue per subsystem
  { name: 'Spectrum', colors: { cortex:'#E8DAC0', white_matter:'#D6DEE8', deep_grey:'#A35CE0', diencephalon:'#5C6CE8', brainstem:'#F0B23C', cerebellum:'#F07A3C', ventricles:'#2CC8C8', arteries:'#F0405C', veins_sinuses:'#3C6CE8', cranial_nerves:'#E0D43C', meninges_dura:'#D44CC4', tracts:'#2CC86A' } },
];

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#F29B8F",
  "stage": "clinical",
  "surface": "frosted",
  "autorotate": true,
  "glow": 1.06
}/*EDITMODE-END*/;

const STAGES = {
  'deep blue':  ['#173849', '#0A1B29', '#040A11', '#F2F7FA', '#91A9B7'],
  'charcoal':   ['#26262b', '#141417', '#08080a', '#ECECEE', '#9b9ba3'],
  'clinical':   ['#dfe6f0', '#c4cedd', '#aebccf', '#1a2030', '#5a667a'],
};

function useIsMobile(bp) {
  bp = bp || 640;
  const q = '(max-width: ' + bp + 'px), (max-height: 520px) and (max-width: 950px)';
  const [m, setM] = React.useState(() => window.matchMedia(q).matches);
  React.useEffect(() => {
    const mq = window.matchMedia(q);
    const h = (e) => setM(e.matches);
    mq.addEventListener ? mq.addEventListener('change', h) : mq.addListener(h);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', h) : mq.removeListener(h); };
  }, []);
  return m;
}

function App() {
  const fissureLabels = {
    70: 'Sylvian fissure (lateral sulcus)', 71: 'Sylvian fissure (lateral sulcus)',
    309: 'Parieto-occipital fissure', 310: 'Parieto-occipital fissure',
    56: 'Central fissure (central sulcus)', 57: 'Central fissure (central sulcus)',
    45: 'Calcarine fissure', 46: 'Calcarine fissure',
    394: 'Transverse fissure', 395: 'Transverse fissure'
  };
  Object.keys(fissureLabels).forEach(id => {
    const node = window.BRAIN.nodes[id];
    if (node) { node.displayLabel = fissureLabels[id]; node.aliases = [fissureLabels[id]]; }
  });
  Object.assign(window.BRAIN.palette, {
    cortex:TISSUE_COLORS.cortex, white_matter:TISSUE_COLORS.white_matter, deep_grey:TISSUE_COLORS.deep_grey, diencephalon:'#8D91CF',
    brainstem:'#D7A06D', cerebellum:TISSUE_COLORS.cortex, ventricles:'#74C9E8', arteries:'#E35D70',
    veins_sinuses:'#667FC4', cranial_nerves:'#D6C765', meninges_dura:'#B96BB1', tracts:'#62B9A8'
  });
  Object.assign(window.BRAIN.descriptions, {
    'Corpus callosum': 'The major commissural bundle connecting the cerebral hemispheres. On a true midsagittal neonatal image, identify the genu anteriorly, body superiorly, and splenium posteriorly. It is an important landmark when assessing brain development and midline formation.',
    'Fornix': 'A paired arching white-matter pathway beneath the corpus callosum. In the neonatal midsagittal plane it carries hippocampal output toward the mammillary bodies.',
    'Septum pellucidum': 'A thin midline membrane between the frontal horns of the lateral ventricles. A cavum septi pellucidi is commonly visible in newborn imaging and is a normal developmental finding when isolated.',
    'Third ventricle': 'The narrow midline CSF cavity between the thalami. In a neonatal midsagittal view it continues posteriorly into the cerebral aqueduct.',
    'Aqueduct of midbrain': 'The narrow CSF channel through the midbrain connecting the third and fourth ventricles. Its position is a useful midsagittal landmark.',
    'Fourth ventricle': 'The CSF cavity between the pons and medulla anteriorly and the cerebellar vermis posteriorly.',
    'Pons': 'The rounded middle segment of the brainstem, anterior to the fourth ventricle. In newborn imaging it is assessed with the midbrain and medulla for symmetry, maturation, and injury.',
    'Medulla oblongata': 'The caudal brainstem segment below the pons. It contains pathways and nuclei essential for breathing and cardiovascular control in the newborn.',
    'Midbrain': 'The rostral brainstem segment surrounding the cerebral aqueduct, between the diencephalon and pons. Its contour helps orient the neonatal midsagittal plane.',
    'Pericallosal artery': 'The distal anterior cerebral artery coursing around the corpus callosum, a useful vascular landmark along the medial cerebral surface.',
    'Superior sagittal sinus': 'The major midline dural venous sinus along the superior margin of the falx cerebri, draining posteriorly toward the confluence of sinuses.',
    'Straight sinus': 'A midline venous channel at the junction of the falx and tentorium, receiving deep cerebral venous drainage.'
    ,'Sylvian fissure (lateral sulcus)': 'The major lateral fissure separating the frontal and parietal lobes above from the temporal lobe below. The insula lies deep within it.'
    ,'Parieto-occipital fissure': 'A prominent medial fissure separating the parietal and occipital lobes.'
    ,'Central fissure (central sulcus)': 'The major sulcus separating the frontal lobe and precentral motor cortex from the parietal lobe and postcentral somatosensory cortex.'
    ,'Calcarine fissure': 'A deep medial occipital fissure whose banks contain the primary visual cortex.'
    ,'Transverse fissure': 'The deep cleft separating the cerebrum from the cerebellum; the tentorium cerebelli occupies this interval.'
    ,'Lateral ventricle': 'Paired CSF spaces whose frontal, body, occipital, and temporal portions are assessed in neonatal coronal, sagittal, and axial imaging. Ventricular size and contour are central to evaluation of hemorrhage and post-hemorrhagic ventricular dilatation.'
    ,'Choroid plexus': 'Vascular tissue that produces CSF. In neonatal imaging it is prominent in the lateral ventricles and is an important landmark when distinguishing normal tissue from intraventricular blood.'
    ,'Caudate nucleus': 'The caudate head lies beside the frontal horn of the lateral ventricle. The caudothalamic groove at its posterior margin is the classic location of the preterm germinal matrix.'
    ,'Putamen': 'The lateral component of the lentiform nucleus. Together with the globus pallidus it is relatively prominent in the term newborn and is separated from the caudate and thalamus by the internal capsule.'
    ,'Globus pallidus external': 'The lateral pallidal segment within the lentiform nucleus. The globus pallidus is relatively prominent in the term newborn and shows early maturation compared with much of the cerebral white matter.'
    ,'Globus pallidus internal': 'The medial pallidal segment within the lentiform nucleus. Its neonatal imaging appearance must be interpreted with the adjacent putamen, thalamus, and posterior limb of the internal capsule.'
    ,'Pulvinar': 'The posterior thalamic region. The thalami are relatively large in the term newborn and form the lateral walls of the third ventricle.'
    ,'Hippocampus': 'A curved medial temporal structure bordering the temporal horn. It is a useful para-sagittal and low axial neonatal landmark and participates in memory circuitry.'
    ,'Pineal gland': 'A small midline gland arising from the roof of the third ventricle, located between the thalami beneath the posterior corpus callosum. It can be identified on thin neonatal sagittal or coronal imaging.'
    ,'Mamillary body': 'A paired hypothalamic landmark inferior to the third ventricle and connected to the hippocampal formation through the fornix.'
    ,'Optic chiasm': 'The midline crossing of the optic pathways, located anterior and inferior to the third ventricle. It is a useful landmark on low axial and midsagittal neonatal views.'
  });
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const mobile = useIsMobile();
  const nodes = window.BRAIN.nodes;
  const cats = window.BRAIN.categories;
  const PAL = window.BRAIN.palette;
  const DESC = window.BRAIN.descriptions;

  // ---- build grouped tree (memo) ----
  const groups = React.useMemo(() => {
    return CAT_ORDER.map(cat => {
      const ns = nodes.filter(n => n.category === cat);
      const byRegion = {};
      ns.forEach(n => { (byRegion[n.region] = byRegion[n.region] || []).push({ id: n.id, label: n.displayLabel || n.label, side: n.side }); });
      const regions = Object.keys(byRegion).sort().map(region => ({
        region, items: byRegion[region].sort((a, b) => a.label.localeCompare(b.label) || a.side.localeCompare(b.side)),
      }));
      return { cat, label: cats[cat].label, count: ns.length, color: PAL[cat], regions };
    });
  }, [palVer]);
  const nodeById = React.useMemo(() => { const m = {}; nodes.forEach(n => m[n.id] = n); return m; }, []);

  // resolve a system stage to real nodeIds (active = this stage, seen = earlier stages)
  const resolveStage = React.useCallback((systemId, stageIndex) => {
    const sys = window.SYS.SYSTEMS.find(s => s.id === systemId);
    if (!sys || !sys.stages[stageIndex]) return { activeIds: [], seenIds: [] };
    const activeKeys = sys.stages[stageIndex].nodes;
    const seenKeys = new Set();
    for (let i = 0; i < stageIndex; i++) sys.stages[i].nodes.forEach(k => seenKeys.add(k));
    const activeIds = [];
    activeKeys.forEach(k => window.SYS.idsForKey(k).forEach(id => activeIds.push(id)));
    const activeSet = new Set(activeIds);
    const seenIds = [];
    seenKeys.forEach(k => { if (!activeKeys.includes(k)) window.SYS.idsForKey(k).forEach(id => { if (!activeSet.has(id)) seenIds.push(id); }); });
    return { activeIds, seenIds };
  }, []);

  // ---- state ----
  const [search, setSearch] = React.useState('');
  const [searchSide, setSearchSide] = React.useState('both');  // filter results to one side
  const [hemisphere, setHemisphere] = React.useState('both');
  const [cortexOpacity, setCortexOpacity] = React.useState(1);
  const [layerOn, setLayerOn] = React.useState(() => { const o = {}; CAT_ORDER.forEach(c => o[c] = ['cortex','deep_grey','diencephalon','ventricles','cerebellum','brainstem'].includes(c)); return o; });
  const [expanded, setExpanded] = React.useState(() => new Set());
  const [selectedId, setSelectedId] = React.useState(null);
  const [isolatedIds, setIsolatedIds] = React.useState(null);
  const [activePreset, setActivePreset] = React.useState('whole');
  const [collapsed, setCollapsed] = React.useState(false);
  const [mobileTool, setMobileTool] = React.useState('view');
  const [pos, setPos] = React.useState(() => { try { return JSON.parse(localStorage.getItem('newborn_brain_console_v1')) || { x: Math.max(16, window.innerWidth - 552), y: 16 }; } catch (e) { return { x: Math.max(16, window.innerWidth - 552), y: 16 }; } });
  const [hover, setHover] = React.useState(null); // {id, x, y}
  const [hint, setHint] = React.useState(true);
  const [posterBusy, setPosterBusy] = React.useState(false);
  const [palVer, setPalVer] = React.useState(0);  // bumps when the color palette changes
  const palIdxRef = React.useRef(0);
  const [focusedId, setFocusedId] = React.useState(null); // which node the camera is focused on

  // ---- modes: explore (structural) · systems (functional) · learn (lessons) ----
  const [mode, setMode] = React.useState('explore');
  const [sceneReady, setSceneReady] = React.useState(false);
  const [slice, setSlice] = React.useState([
    { on: false, value: 0, flip: false },
    { on: false, value: 0, flip: false },
    { on: false, value: 0, flip: false },
  ]);
  const [arterialMode, setArterialMode] = React.useState(false);
  const [venousMode, setVenousMode] = React.useState(false);
  const vascularCortexRef = React.useRef(1);
  // VR (WebXR beta)
  const [vrSupported, setVrSupported] = React.useState(false);
  const [vrActive, setVrActive] = React.useState(false);
  const vrActionRef = React.useRef(null);   // current handler for in-VR panel button presses (kept fresh each render)
  // systems
  const [activeSystem, setActiveSystem] = React.useState(null);
  const [sysStep, setSysStep] = React.useState(0);
  const [sysPlaying, setSysPlaying] = React.useState(false);
  // lessons
  const [openLessonId, setOpenLessonId] = React.useState(null);
  const [phase, setPhase] = React.useState(null);          // 'intro' | 'playing' | 'quiz' | 'done'
  const [lessonStep, setLessonStep] = React.useState(0);
  const [lessonPlaying, setLessonPlaying] = React.useState(false);
  const [qIdx, setQIdx] = React.useState(0);
  const [answers, setAnswers] = React.useState({});
  const [quizSet, setQuizSet] = React.useState([]);            // questions in the current round (drawn from the bank)
  const [quizTally, setQuizTally] = React.useState({ asked: 0, correct: 0 });  // running totals across rounds
  const quizSeenRef = React.useRef(new Set());                 // bank questions already used this session (avoid repeats)
  const [completed, setCompleted] = React.useState(() => {
    try { return new Set(JSON.parse(localStorage.getItem('ba_completed') || '[]')); } catch (e) { return new Set(); }
  });
  const [toast, setToast] = React.useState('');
  const flash = (m) => { setToast(m); clearTimeout(window.__tt); window.__tt = setTimeout(() => setToast(''), 2200); };
  const quizPickRef = React.useRef(null);     // LessonQuiz answer() for "find" questions
  const quizFindRef = React.useRef(null);     // { idToKey } while a find-question is live

  React.useEffect(() => { try { localStorage.setItem('ba_completed', JSON.stringify([...completed])); } catch (e) {} }, [completed]);

  const [consent, setConsent] = React.useState(() => (window.BrainAnalytics ? window.BrainAnalytics.consent() : 'denied'));
  const acceptCookies = () => { window.BrainAnalytics && window.BrainAnalytics.grant(); setConsent('granted'); };
  const declineCookies = () => { window.BrainAnalytics && window.BrainAnalytics.deny(); setConsent('denied'); };

  React.useEffect(() => { try { localStorage.setItem('newborn_brain_console_v1', JSON.stringify(pos)); } catch (e) {} }, [pos]);

  const sceneRef = React.useRef(null);
  const canvasRef = React.useRef(null);

  // ---- search matching ----
  const q = search.trim();
  const matchedIds = React.useMemo(() => {
    const s = new Set();
    if (!q) return s;
    const lq = q.toLowerCase();
    nodes.forEach(n => {
      // side filter: midline structures have no L/R counterpart, so keep them on either side
      if (searchSide !== 'both' && n.side !== 'median' && n.side !== searchSide) return;
      if (n.label.toLowerCase().includes(lq) || (n.displayLabel || '').toLowerCase().includes(lq) ||
          (n.aliases || []).some(alias => alias.toLowerCase().includes(lq)) || n.region.toLowerCase().includes(lq) ||
          (n.crumb || []).some(c => c.toLowerCase().includes(lq)) || n.category.includes(lq)) s.add(n.id);
    });
    return s;
  }, [q, searchSide]);
  const matchedCats = React.useMemo(() => { const c = new Set(); matchedIds.forEach(id => c.add(nodeById[id].category)); return c; }, [matchedIds]);

  // auto-expand categories with matches
  React.useEffect(() => {
    if (q) setExpanded(new Set(matchedCats));
  }, [q]);

  // ---- scene init ----
  React.useEffect(() => {
    const s = window.BrainScene.create(canvasRef.current, {
      autorotate: t.autorotate,
      onReady: () => setSceneReady(true),
      onPick: (id) => {
        // during a lesson "find this structure" question, a click answers it
        if (id != null && quizFindRef.current) {
          const key = quizFindRef.current.idToKey[id];
          if (key && quizPickRef.current) quizPickRef.current(key);
          return;
        }
        if (id != null) { setSelectedId(id); setHint(false); } else setSelectedId(null);
      },
      onHover: (id, meta) => setHover(h => meta ? meta : (id != null ? { id } : null)),
      onVR: (active) => setVrActive(active),
      onVRAction: (type, val) => vrActionRef.current && vrActionRef.current(type, val),
    });
    sceneRef.current = s; window.__BA = s;
    s.vr.supported().then(setVrSupported).catch(() => {});
    return () => s.dispose();
  }, []);

  React.useEffect(() => {
    const s = sceneRef.current;
    if (!s || !s.setSlice) return;
    slice.forEach((v, axis) => s.setSlice(axis, v.on, v.value));
  }, [slice, sceneReady]);

  React.useEffect(() => {
    const s = sceneRef.current;
    if (s && s.setVenousOverlay) s.setVenousOverlay(venousMode);
  }, [venousMode, sceneReady]);

  // ---- compose & push to scene ----
  const firstCompose = React.useRef(true);
  React.useEffect(() => {
    const s = sceneRef.current; if (!s) return;
    const map = {};
    const isoCats = isolatedIds ? new Set([...isolatedIds].map(id => nodeById[id].category)) : null;
    CAT_ORDER.forEach(cat => {
      let visible = layerOn[cat];
      let opacity = cat === 'cortex' ? cortexOpacity : 1;
      if (q && !matchedCats.has(cat)) opacity *= 0.12;
      if (isoCats) { if (!isoCats.has(cat)) visible = false; else opacity = 1; } // isolated layer fully opaque
      map[cat] = { visible, opacity };
    });
    s.setLayers(map);
    s.setHemisphere(hemisphere);
    s.isolate(isolatedIds ? [...isolatedIds] : null);
    // preset-defined sub-selection (e.g. Circle of Willis = only the ring arteries)
    const preset = PRESETS.find(p => p.id === activePreset);
    let subsetMap = null;
    if (preset && preset.subset) {
      subsetMap = {};
      Object.keys(preset.subset).forEach(cat => {
        const labels = new Set(preset.subset[cat]);
        subsetMap[cat] = new Set(nodes.filter(n => n.category === cat && labels.has(n.label)).map(n => n.id));
      });
    }
    s.setSubset(subsetMap);
    if (selectedId != null) s.selectNode(selectedId); else s.clearSelect();
    if (firstCompose.current) { s.snap(); firstCompose.current = false; }
  }, [layerOn, cortexOpacity, hemisphere, q, matchedCats, isolatedIds, selectedId, activePreset]);

  React.useEffect(() => { sceneRef.current && sceneRef.current.setAutoRotate(t.autorotate); }, [t.autorotate]);
  React.useEffect(() => { sceneRef.current && sceneRef.current.setExposure(t.glow); }, [t.glow]);

  // ---- tweaks -> CSS vars ----
  React.useEffect(() => {
    const r = document.documentElement.style;
    r.setProperty('--accent', t.accent);
    r.setProperty('--accent-soft', t.accent + '1f');
    r.setProperty('--accent-ring', t.accent + '66');
    const sg = STAGES[t.stage] || STAGES['deep blue'];
    r.setProperty('--stage-1', sg[0]); r.setProperty('--stage-2', sg[1]); r.setProperty('--stage-3', sg[2]);
    r.setProperty('--on-stage', sg[3]); r.setProperty('--on-stage-soft', sg[4]);
    if (t.surface === 'solid') { r.setProperty('--glass', 'rgba(248,250,252,.99)'); }
    else { r.setProperty('--glass', 'rgba(246,249,252,.91)'); }
  }, [t.accent, t.stage, t.surface]);

  // ---- actions ----
  const toggleLayer = (cat) => { setActivePreset(null); setLayerOn(o => ({ ...o, [cat]: !o[cat] })); };
  const onExpand = (cat) => setExpanded(s => { const n = new Set(s); n.has(cat) ? n.delete(cat) : n.add(cat); return n; });
  const showAll = () => { setActivePreset(null); setLayerOn(() => { const o = {}; CAT_ORDER.forEach(c => o[c] = true); return o; }); setDepthStop(0); };
  const hideAll = () => { setActivePreset(null); setLayerOn(() => { const o = {}; CAT_ORDER.forEach(c => o[c] = false); return o; }); };
  const selectNode = (id) => { setSelectedId(id); setHint(false); };
  const focusNode = (id) => { sceneRef.current && sceneRef.current.focusNode(id); setSelectedId(id); setFocusedId(id); setHint(false); };
  // toggling focus for the selection card: a second press is its own undo (reset view)
  const toggleFocus = (id) => {
    if (focusedId === id) { sceneRef.current && sceneRef.current.reset(); setFocusedId(null); }
    else { sceneRef.current && sceneRef.current.focusNode(id); setFocusedId(id); }
  };
  const focusCat = (cat) => sceneRef.current && sceneRef.current.focusCategory(cat);
  const zoom = (dir) => sceneRef.current && sceneRef.current.zoom(dir);
  const enterVR = () => {
    const s = sceneRef.current; if (!s) return;
    s.vr.enter().catch(err => flash('Could not start VR: ' + ((err && err.message) || err)));
  };
  const exitVR = () => { const s = sceneRef.current; if (s) s.vr.exit(); };
  const changeSlice = (next) => {
    const newlyEnabled = next.findIndex((v, i) => v.on && !slice[i].on);
    setSlice(next);
    if (newlyEnabled >= 0) {
      const anatomicalViews = ['sagittal', 'axial', 'coronal'];
      // A section must include the tissue compartments hidden in the intact
      // overview; otherwise the space between cortex and nuclei looks hollow.
      setLayerOn(prev => ({ ...prev, cortex: true, white_matter: true, deep_grey: true,
        diencephalon: true, ventricles: true, brainstem: true, cerebellum: true }));
      setTweak('autorotate', false);
      if (sceneRef.current) {
        sceneRef.current.setAutoRotate(false);
        sceneRef.current.setView(anatomicalViews[newlyEnabled]);
      }
    }
  };
  const selectAnatomicalView = (view) => {
    setTweak('autorotate', false);
    if (sceneRef.current) {
      sceneRef.current.setAutoRotate(false);
      sceneRef.current.setView(view);
    }
  };
  const reset = () => {
    setActivePreset('whole'); applyPreset(PRESETS[0], false);
    setHemisphere('both'); setSearchSide('both'); setIsolatedIds(null); setSearch(''); setSelectedId(null); setFocusedId(null);
    setSlice([{ on: false, value: 0, flip: false }, { on: false, value: 0, flip: false }, { on: false, value: 0, flip: false }]);
    setArterialMode(false);
    setVenousMode(false);
    setMode('explore'); setActiveSystem(null); setOpenLessonId(null); setPhase(null); setSysPlaying(false); setLessonPlaying(false);
    if (sceneRef.current) {
      sceneRef.current.clearHighlight(); sceneRef.current.setView('threequarter'); sceneRef.current.reset();
      sceneRef.current.setAutoRotate(t.autorotate);
    }
    flash('Global brain view restored');
  };

  const toggleArterialMode = () => {
    setArterialMode(current => {
      const next = !current;
      setActivePreset(null);
      setLayerOn(layers => ({ ...layers, arteries: next, veins_sinuses: next ? false : layers.veins_sinuses }));
      if (next) {
        if (!venousMode) vascularCortexRef.current = cortexOpacity;
        setVenousMode(false);
        setCortexOpacity(.24);
      } else if (!venousMode) setCortexOpacity(vascularCortexRef.current);
      flash(next ? 'Cerebral arterial system and supply territories added' : 'Cerebral arterial system removed');
      return next;
    });
  };

  const toggleVenousMode = () => {
    setVenousMode(current => {
      const next = !current;
      setActivePreset(null);
      setLayerOn(layers => ({ ...layers, veins_sinuses: next, arteries: next ? false : layers.arteries }));
      if (next) {
        if (!arterialMode) vascularCortexRef.current = cortexOpacity;
        setArterialMode(false);
        setCortexOpacity(.42);
      } else if (!arterialMode) setCortexOpacity(vascularCortexRef.current);
      flash(next ? 'Cerebral venous system and drainage territories added' : 'Cerebral venous system removed');
      return next;
    });
  };

  const applyPreset = (p, doFocus = true) => {
    setActivePreset(p.id);
    setIsolatedIds(null); setSearch(''); setSelectedId(null); setFocusedId(null);  // close any open selection card
    const o = {}; CAT_ORDER.forEach(c => o[c] = p.on.includes(c) || (c === 'cortex' && p.cortex > 0));
    setLayerOn(o); setCortexOpacity(p.cortex);
    if (p.slice) {
      setHemisphere('both');
      const volumeLabelIds = {
        'Corpus callosum': [48], 'Lateral ventricle': [49, 50], 'Third ventricle': [49, 50],
        'Hippocampus': [1, 2], 'Amygdala': [3, 4], 'Thalamus': [42, 43, 86, 87]
      };
      const requestedLabels = p.slice.targets || (p.slice.target ? [p.slice.target] : []);
      const structureIds = requestedLabels.flatMap(label => volumeLabelIds[label] || []);
      const atlasPosition = window.NeonatalVolume && window.NeonatalVolume.positionForStructures(structureIds, p.slice.axis);
      const sliceValue = Number.isFinite(atlasPosition) ? atlasPosition : (p.slice.value || 0);
      setSlice([0,1,2].map(axis => ({ on: axis === p.slice.axis, value: axis === p.slice.axis ? sliceValue : 0, flip: false })));
      if (structureIds.length && window.NeonatalVolume) {
        window.NeonatalVolume.load().then(() => {
          const resolved = window.NeonatalVolume.positionForStructures(structureIds, p.slice.axis);
          if (Number.isFinite(resolved)) setSlice([0,1,2].map(axis => ({ on: axis === p.slice.axis, value: axis === p.slice.axis ? resolved : 0, flip: false })));
        }).catch(() => {});
      }
      setTweak('autorotate', false);
      setTimeout(() => {
        if (sceneRef.current) { sceneRef.current.setAutoRotate(false); sceneRef.current.setView(p.view); }
      }, 60);
    } else setSlice([{ on: false, value: 0, flip: false }, { on: false, value: 0, flip: false }, { on: false, value: 0, flip: false }]);
    if (doFocus && p.focus) setTimeout(() => sceneRef.current && sceneRef.current.focusCategory(p.focus), 60);
    else if (doFocus) sceneRef.current && sceneRef.current.reset();
  };

  // ============================================================
  //  Systems & Lessons
  // ============================================================
  const lesson = openLessonId ? window.SYS.LESSONS.find(l => l.id === openLessonId) : null;
  const lessonSysId = lesson ? lesson.system : null;

  // which pathway stage is currently on the stage (system free-stepping, or a playing lesson)
  let stageActive = null;
  if (lesson && (phase === 'intro' || phase === 'playing')) stageActive = { systemId: lessonSysId, stageIndex: phase === 'intro' ? 0 : lessonStep };
  else if (activeSystem) stageActive = { systemId: activeSystem, stageIndex: sysStep };

  // lesson "find this structure" challenge -> candidate keys glow on the brain
  let quizFind = null;
  const curQ = phase === 'quiz' ? quizSet[qIdx] : null;
  if (lesson && curQ && curQ.type === 'find' && answers[qIdx] === undefined) {
    quizFind = { keys: curQ.options, answerKey: curQ.answer };
  }
  const playerOpen = !!stageActive || (lesson && (phase === 'quiz' || phase === 'done'));

  const stageKey = stageActive ? stageActive.systemId + ':' + stageActive.stageIndex : 'none';
  const quizKey = quizFind ? openLessonId + ':q' + qIdx : 'none';

  // ---- drive the real 3D scene from the active pathway stage / quiz ----
  React.useEffect(() => {
    const s = sceneRef.current; if (!s) return;
    if (quizFind) {
      const ids = []; const idToKey = {};
      quizFind.keys.forEach(k => window.SYS.idsForKey(k).forEach(id => { ids.push(id); idToKey[id] = k; }));
      quizFindRef.current = { idToKey };
      s.setHighlight(ids, []);
      s.frameNodes(ids, 2.8);
    } else if (stageActive) {
      quizFindRef.current = null;
      const { activeIds, seenIds } = resolveStage(stageActive.systemId, stageActive.stageIndex);
      s.setHighlight(activeIds, seenIds);
      s.frameNodes(activeIds.length ? activeIds : seenIds, 3.0);
    } else {
      quizFindRef.current = null;
      s.clearHighlight();
    }
  }, [stageKey, quizKey, sceneReady]);

  // ---- system actions ----
  const startSystem = (id) => {
    if (activeSystem === id) { setActiveSystem(null); setSysPlaying(false); if (!mobile) setCollapsed(false); return; }
    setActiveSystem(id); setSysStep(0); setSysPlaying(false); setSelectedId(null);
    if (!mobile) setCollapsed(true);   // focus on the model + narration card
  };
  const closeSystem = () => { reset(); if (!mobile) setCollapsed(false); };

  // ---- quiz round drawing (a short randomised round pulled from the lesson's bank) ----
  const QUIZ_ROUND = 4;   // questions asked per round before we offer "more"
  const shuffle = (arr) => {
    const a = arr.slice();
    for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
    return a;
  };
  // draw the next round of unseen questions; once the bank is exhausted, reshuffle the whole bank
  const drawRound = (bank) => {
    const want = Math.min(QUIZ_ROUND, bank.length);
    let pool = bank.filter(q => !quizSeenRef.current.has(q));
    if (pool.length < want) { quizSeenRef.current = new Set(); pool = bank.slice(); }
    const round = shuffle(pool).slice(0, want);
    round.forEach(q => quizSeenRef.current.add(q));
    return round;
  };
  const startQuizRound = (bank) => {
    setQuizSet(drawRound(bank)); setQIdx(0); setAnswers({}); setPhase('quiz');
  };

  // ---- lesson actions ----
  const openLesson = (id) => {
    if (!window.SYS.LESSONS.some(l => l.id === id)) return;
    quizSeenRef.current = new Set();
    setOpenLessonId(id); setPhase('intro'); setLessonStep(0); setLessonPlaying(false);
    setQIdx(0); setAnswers({}); setQuizSet([]); setQuizTally({ asked: 0, correct: 0 });
    setSelectedId(null); setActiveSystem(null); setMode('learn');
    if (!mobile) setCollapsed(true);
  };
  const beginLesson = () => { setPhase('playing'); setLessonStep(0); setLessonPlaying(true); };
  const finishStages = () => {
    if (lesson && lesson.quiz && lesson.quiz.length) { setLessonPlaying(false); startQuizRound(lesson.quiz); }
    else completeLesson();
  };
  // finish the current round: bank this round's score into the running tally, then show completion
  const completeLesson = () => {
    const correct = quizSet.reduce((a, q, i) => a + (answers[i] === q.answer ? 1 : 0), 0);
    setQuizTally(t => ({ asked: t.asked + quizSet.length, correct: t.correct + correct }));
    setLessonPlaying(false); setPhase('done');
    setCompleted(c => new Set([...c, openLessonId]));
  };
  // "Ask me more": draw a fresh round from the bank and return to the quiz
  const moreQuestions = () => { if (lesson && lesson.quiz) { setLessonPlaying(false); startQuizRound(lesson.quiz); } };
  const closeLesson = () => { setOpenLessonId(null); setPhase(null); setLessonPlaying(false); if (!mobile) setCollapsed(false); sceneRef.current && sceneRef.current.reset(); };
  const replayLesson = () => { quizSeenRef.current = new Set(); setQuizTally({ asked: 0, correct: 0 }); setQuizSet([]); setPhase('playing'); setLessonStep(0); setQIdx(0); setAnswers({}); setLessonPlaying(true); };

  // ---- mode switching (stop whatever the other modes were running) ----
  const switchMode = (m) => {
    setMode(m);
    if (m !== 'systems') { setActiveSystem(null); setSysPlaying(false); }
    if (m !== 'learn') { setOpenLessonId(null); setPhase(null); setLessonPlaying(false); }
    if (m === 'explore') { setSelectedId(null); }
  };

  const applyPalette = (pal) => {
    pal = { ...pal, ...TISSUE_COLORS };                             // preserve anatomical tissue convention
    Object.assign(window.BRAIN.palette, pal);                       // mutate in place so PAL ref sees it
    const r = document.documentElement.style;
    Object.keys(pal).forEach(k => r.setProperty('--c-' + k, pal[k]));
    sceneRef.current && sceneRef.current.setPalette(pal);
    setPalVer(v => v + 1);                                          // re-render dots/legend/tree
  };
  const randomizePalette = () => {
    let j; do { j = Math.floor(Math.random() * PALETTES.length); } while (j === palIdxRef.current && PALETTES.length > 1);
    palIdxRef.current = j;
    applyPalette(PALETTES[j].colors);
  };

  const isolateMatches = () => { if (matchedIds.size) setIsolatedIds(new Set(matchedIds)); };
  const isolateNode = (id) => {
    // isolate exactly the clicked structure (both sides if it is one of a pair)
    const n = nodeById[id];
    const ids = nodes.filter(x => x.label === n.label && x.category === n.category).map(x => x.id);
    setIsolatedIds(new Set(ids.length ? ids : [id]));
    setTimeout(() => sceneRef.current && sceneRef.current.focusNode(id), 60);
  };
  const clearIsolate = () => setIsolatedIds(null);

  // ---- selection derived ----
  const selNode = selectedId != null ? nodeById[selectedId] : null;
  const displaySelNode = selNode ? { ...selNode, label: selNode.displayLabel || selNode.label } : null;
  const related = React.useMemo(() => {
    if (!selNode) return [];
    // only suggest structures that are actually visible on the stage: when a single
    // hemisphere is shown, drop the opposite side (median structures show on both).
    const sideOk = (s) => hemisphere === 'both' || s === 'median' || s === hemisphere;
    return nodes.filter(n => n.id !== selNode.id && n.category === selNode.category && n.region === selNode.region && sideOk(n.side))
      .slice(0, 5).map(n => ({ id: n.id, label: n.displayLabel || n.label, side: n.side }));
  }, [selectedId, hemisphere]);
  const description = selNode ? (DESC[selNode.displayLabel || selNode.label] || DESC[selNode.label] || ('A structure of the ' + cats[selNode.category].label.toLowerCase() + ', located in the ' + selNode.region.replace(/_/g, ' ') + '.')) : '';

  // mirror the selection into the in-headset 3D info panel (the HTML card isn't visible in VR),
  // including the same Related-structures buttons the web card shows
  React.useEffect(() => {
    const s = sceneRef.current; if (!s || !s.setVRInfo) return;
    if (selNode) {
      const side = selNode.side === 'median' ? 'Midline' : (selNode.side === 'left' ? 'Left' : 'Right');
      s.setVRInfo({ title: selNode.displayLabel || selNode.label, side, body: description, color: PAL[selNode.category], related });
    } else {
      s.setVRInfo(null);
    }
  }, [selectedId, description, related]);

  // mirror the control panel (hemisphere / layers / views) into the left in-VR panel
  React.useEffect(() => {
    const s = sceneRef.current; if (!s || !s.setVRControls || !vrActive) return;
    s.setVRControls({
      hemisphere,
      layers: CAT_ORDER.map(cat => ({ cat, label: SHORT[cat] || (cats[cat] && cats[cat].label) || cat, on: !!layerOn[cat], color: PAL[cat] })),
      presets: PRESETS.map(p => ({ id: p.id, label: p.label, active: activePreset === p.id })),
    });
  }, [vrActive, hemisphere, layerOn, activePreset]);

  // mirror the Systems/Learn step card into the in-VR narration panel (below the brain)
  React.useEffect(() => {
    const s = sceneRef.current; if (!s || !s.setVRNarration || !vrActive) return;
    let narr = null;
    if (lesson && phase === 'playing') {
      const sys = window.SYS.SYSTEMS.find(x => x.id === lessonSysId);
      if (sys && sys.stages[lessonStep]) { const st = sys.stages[lessonStep]; narr = { kind: 'Lesson', title: st.title, body: st.body, step: lessonStep, total: sys.stages.length, last: lessonStep >= sys.stages.length - 1, isLesson: true }; }
    } else if (activeSystem) {
      const sys = window.SYS.SYSTEMS.find(x => x.id === activeSystem);
      if (sys && sys.stages[sysStep]) { const st = sys.stages[sysStep]; narr = { kind: 'System', title: st.title, body: st.body, step: sysStep, total: sys.stages.length, last: sysStep >= sys.stages.length - 1, isLesson: false }; }
    }
    s.setVRNarration(narr);
  }, [vrActive, activeSystem, sysStep, openLessonId, phase, lessonStep, lessonSysId]);

  // handle button presses coming back from the in-VR panels (kept fresh each render so it
  // closes over current state). Routes to the same handlers the web UI uses.
  const vrNarrStep = (dir) => {
    if (lesson && phase === 'playing') {
      const sys = window.SYS.SYSTEMS.find(x => x.id === lessonSysId); const len = sys ? sys.stages.length : 0;
      if (dir > 0) { if (lessonStep >= len - 1) finishStages(); else setLessonStep(lessonStep + 1); } else setLessonStep(Math.max(0, lessonStep - 1));
    } else if (activeSystem) {
      const sys = window.SYS.SYSTEMS.find(x => x.id === activeSystem); const len = sys ? sys.stages.length : 0;
      setSysStep(dir > 0 ? Math.min(len - 1, sysStep + 1) : Math.max(0, sysStep - 1));
    }
  };
  vrActionRef.current = (type, val) => {
    switch (type) {
      case 'exit': exitVR(); break;
      case 'select': selectNode(val); break;
      case 'toggleLayer': toggleLayer(val); break;
      case 'hemisphere': setHemisphere(val); break;
      case 'preset': { const p = PRESETS.find(x => x.id === val); if (p) applyPreset(p); break; }
      case 'showAll': showAll(); break;
      case 'hideAll': hideAll(); break;
      case 'reset': reset(); break;
      case 'narrPrev': vrNarrStep(-1); break;
      case 'narrNext': vrNarrStep(1); break;
      case 'narrClose': if (openLessonId) closeLesson(); else closeSystem(); break;
      default: break;
    }
  };

  // ---- high-definition poster export (features the selection if any) ----
  const savePoster = async () => {
    const s = sceneRef.current; if (!s || posterBusy) return;
    setPosterBusy(true);
    try {
      if (selectedId != null) { s.focusNode(selectedId); await new Promise(r => setTimeout(r, 750)); }
      const title = selNode ? (selNode.displayLabel || selNode.label) : 'Newborn Brain 3D';
      const sub = selNode
        ? ((selNode.crumb && selNode.crumb.length ? selNode.crumb.join('  ·  ') : cats[selNode.category].label)
           + (selNode.side !== 'median' ? '  ·  ' + (selNode.side === 'left' ? 'Left' : 'Right') : ''))
        : 'The most detailed interactive 3D brain';
      const url = s.capturePoster(2400, 1500, { title, subtitle: sub, color: selNode ? PAL[selNode.category] : t.accent });
      const a = document.createElement('a');
      a.href = url;
      a.download = 'newborn-brain-3d-' + title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') + '.png';
      document.body.appendChild(a); a.click(); a.remove();
    } catch (e) { console.error('poster failed', e); }
    setPosterBusy(false);
  };

  // ---- shareable deep link (GitHub Pages friendly: plain query params) ----
  const buildShareUrl = () => {
    const p = new URLSearchParams();
    p.set('mode', mode);
    if (mode === 'systems' && activeSystem) { p.set('sys', activeSystem); p.set('step', String(sysStep)); }
    else if (mode === 'learn' && openLessonId) { p.set('lesson', openLessonId); }
    else {
      if (activePreset && activePreset !== 'whole') p.set('preset', activePreset);
      if (hemisphere !== 'both') p.set('hemi', hemisphere);
      if (selectedId != null) {
        p.set('sel', String(selectedId));
        if (focusedId === selectedId) p.set('focus', '1');   // shared from focused mode -> reopen focused
      }
    }
    return location.origin + location.pathname + '?' + p.toString();
  };
  const copyShareLink = async () => {
    const url = buildShareUrl();
    try { await navigator.clipboard.writeText(url); flash('Link copied to clipboard'); }
    catch (e) { window.prompt('Copy this link', url); }
  };

  // restore state from a shared link (once, on mount). The deep-linked structure is
  // applied later, once the specimen has loaded, since the camera can't frame a mesh
  // before then - see the sceneReady effect below.
  const didRestore = React.useRef(false);
  const pendingDeep = React.useRef(null);   // { id, focus } parsed from the link
  React.useEffect(() => {
    if (didRestore.current) return; didRestore.current = true;
    let p; try { p = new URLSearchParams(location.search); } catch (e) { return; }
    if (![...p.keys()].length) return;
    if (p.get('sys')) {
      const id = p.get('sys');
      if (window.SYS.SYSTEMS.some(s => s.id === id)) {
        setMode('systems'); setActiveSystem(id); if (!mobile) setCollapsed(true);
        const st = parseInt(p.get('step') || '0', 10); if (!isNaN(st)) setSysStep(st);
      }
    } else if (p.get('lesson')) {
      openLesson(p.get('lesson'));
    } else {
      if (p.get('mode')) setMode(p.get('mode'));
      if (p.get('preset')) { const pr = PRESETS.find(x => x.id === p.get('preset')); if (pr) applyPreset(pr); }
      if (p.get('hemi')) setHemisphere(p.get('hemi'));
      if (p.get('sel')) { const id = parseInt(p.get('sel'), 10); if (!isNaN(id) && nodeById[id]) pendingDeep.current = { id, focus: p.get('focus') === '1' }; }
    }
  }, []);

  // apply the deep-linked structure once the specimen is loaded: if the link was shared
  // from focused mode, focus it (camera + select + active focus toggle); otherwise just
  // select it, exactly as a click would.
  React.useEffect(() => {
    if (!sceneReady || !pendingDeep.current) return;
    const { id, focus } = pendingDeep.current; pendingDeep.current = null;
    if (focus) focusNode(id); else selectNode(id);
  }, [sceneReady]);

  // ---- keyboard ----
  React.useEffect(() => {
    const h = (e) => {
      if (e.target.tagName === 'INPUT') { if (e.key === 'Escape') e.target.blur(); return; }
      if (e.key === '/') { e.preventDefault(); const i = document.querySelector('.glass input'); i && i.focus(); }
      else if (e.key === 'Escape') { if (isolatedIds) clearIsolate(); else setSelectedId(null); }
      else if (e.key === '1') switchMode('explore');
      else if (e.key === '2') switchMode('systems');
      else if (e.key === '3') switchMode('learn');
      else if (e.key.toLowerCase() === 'r') reset();
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [isolatedIds]);

  const hoverNode = hover ? (hover.label ? hover : nodeById[hover.id]) : null;

  return (
    <React.Fragment>
      <div className="stage" />
      <canvas ref={canvasRef} className="three" />
      {slice.some(v => v.on) && (
        <NeonatalSliceView slice={slice} layerOn={layerOn} arterialMode={arterialMode} venousMode={venousMode} />
      )}

      {/* corner toolbar: save poster · credits · shuffle palette · subsystem key */}
      <Legend groups={groups} layerOn={layerOn} onPoster={savePoster} posterBusy={posterBusy} onCopyLink={copyShareLink} onZoom={zoom} mobile={mobile}
        autorotate={slice.some(v => v.on) ? false : t.autorotate}
        onToggleSpin={() => slice.some(v => v.on) ? flash('Rotation is paused in MRI section view') : setTweak('autorotate', !t.autorotate)}
        vrSupported={vrSupported} vrActive={vrActive} onEnterVR={enterVR} onExitVR={exitVR} />

      {hint && consent && !mobile && !slice.some(v => v.on) && (
        <div className="pop" style={{ position: 'absolute', bottom: 18, left: '50%', transform: 'translateX(-50%)', zIndex: 12,
          display: 'flex', alignItems: 'center', gap: 8, padding: '8px 14px', borderRadius: 99,
          background: 'rgba(8,11,18,0.72)', color: '#F8FAFC', fontSize: 12.5, fontWeight: 600, backdropFilter: 'blur(8px)',
          border: '1px solid rgba(255,255,255,0.08)' }}>
          <Icon name="crosshair" size={14} style={{ color: '#D5DEE7' }} />
          Drag to rotate · scroll to zoom · click a structure to inspect
        </div>
      )}

      {(!mobile || mobileTool === 'anatomy') && <ControlPanel
        mode={mode} setMode={switchMode}
        activeSystem={activeSystem} onStartSystem={startSystem}
        completedSet={completed} onOpenLesson={openLesson}
        totalCount={nodes.length}
        groups={groups} layerOn={layerOn} expanded={expanded}
        onToggleLayer={toggleLayer} onExpand={onExpand}
        onSelect={selectNode} onFocus={focusNode} onFocusCat={focusCat}
        selectedId={selectedId}
        search={search} setSearch={(v) => { setSearch(v); setActivePreset(null); }} matchedIds={matchedIds} matchCount={matchedIds.size}
        searchSide={searchSide} setSearchSide={setSearchSide}
        hemisphere={hemisphere} setHemisphere={(v) => { setHemisphere(v); }}
        cortexOpacity={cortexOpacity} setCortexOpacity={(v) => { setCortexOpacity(v); setActivePreset(null); }}
        presets={PRESETS} activePreset={activePreset} onPreset={(id) => applyPreset(PRESETS.find(p => p.id === id))}
        landmarkProfile={NEONATAL_LANDMARKS[activePreset] || null}
        onReset={reset} onShowAll={showAll} onHideAll={hideAll}
        onIsolateMatches={isolateMatches} canIsolate={matchedIds.size > 0}
        isolated={!!isolatedIds} onClearIsolate={clearIsolate}
        pos={pos} setPos={setPos} collapsed={collapsed} setCollapsed={setCollapsed}
        q={q} mobile={mobile}
      />}

      {!consent && <ConsentBanner onAccept={acceptCookies} onDecline={declineCookies} />}

      <SelectionCard node={displaySelNode} color={selNode ? PAL[selNode.category] : null}
        catLabel={selNode ? cats[selNode.category].label : ''} description={description} related={related}
        lessons={selNode ? window.SYS.lessonsForLabel(selNode.label) : []}
        onOpenLesson={(id) => { setSelectedId(null); openLesson(id); }}
        onSelect={selectNode} onRelated={focusNode} onFocus={toggleFocus} onIsolate={isolateNode} onClose={() => setSelectedId(null)}
        isolated={!!isolatedIds} focused={selNode && focusedId === selNode.id} onClearIsolate={clearIsolate} mobile={mobile} />

      {(!mobile || mobileTool === 'slices') && <SlicePanel value={slice} onChange={changeSlice}
        onView={selectAnatomicalView} layerOn={layerOn} onToggleLayer={toggleLayer}
        arterialMode={arterialMode} onToggleArterialMode={toggleArterialMode}
        venousMode={venousMode} onToggleVenousMode={toggleVenousMode} mobile={mobile} />}

      {mobile && !selectedId && (
        <nav className="mobile-dock glass" aria-label="Mobile atlas tools">
          <button className={mobileTool === 'view' ? 'active' : ''} onClick={() => setMobileTool('view')}>
            <Icon name="eye" size={17} /><span>View</span>
          </button>
          <button className={mobileTool === 'anatomy' ? 'active' : ''} onClick={() => setMobileTool('anatomy')}>
            <Icon name="brain" size={17} /><span>Anatomy</span>
          </button>
          <button className={mobileTool === 'slices' ? 'active' : ''} onClick={() => setMobileTool('slices')}>
            <Icon name="layers" size={17} /><span>MRI slices</span>
          </button>
        </nav>
      )}

      <footer className={`project-credit${mobile ? ' mobile' : ''}`}>
        Developed by Khorshid Mohammad ·{' '}
        <a href="https://sarnatnncc.ca/" target="_blank" rel="noopener noreferrer">The Harvey Sarnat NNCC Care Program</a> ·{' '}
        <a href="https://sarnatnncc.ca/innovation.html" target="_blank" rel="noopener noreferrer">Dr. Luis Bello-Espinosa Innovation in NNCC Lab</a>
      </footer>

      {/* SYSTEMS narration (free stepping) */}
      {activeSystem && !lesson && (
        <NarrationCard sys={window.SYS.SYSTEMS.find(s => s.id === activeSystem)} idx={sysStep} setIdx={setSysStep}
          playing={sysPlaying} setPlaying={setSysPlaying} onClose={closeSystem} onSelectNode={selectNode} isLesson={false} mobile={mobile} />
      )}

      {/* LESSON player chrome */}
      {lesson && phase === 'intro' && <LessonIntro lesson={lesson} onBegin={beginLesson} onClose={closeLesson} mobile={mobile} />}
      {lesson && phase === 'playing' && (
        <NarrationCard sys={window.SYS.SYSTEMS.find(s => s.id === lessonSysId)} idx={lessonStep} setIdx={setLessonStep}
          playing={lessonPlaying} setPlaying={setLessonPlaying} onClose={closeLesson} onSelectNode={selectNode}
          isLesson={true} lessonTitle={lesson.title} onFinish={finishStages} mobile={mobile} />
      )}
      {lesson && phase === 'quiz' && (
        <LessonQuiz lesson={lesson} quiz={quizSet} qIdx={qIdx} setQIdx={setQIdx} answers={answers} setAnswers={setAnswers}
          onFinish={completeLesson} pickFromStage={quizPickRef} mobile={mobile} />
      )}
      {lesson && phase === 'done' && (
        <LessonComplete lesson={lesson} score={quizSet.reduce((a, q, i) => a + (answers[i] === q.answer ? 1 : 0), 0)}
          total={quizSet.length} tally={quizTally} bankSize={lesson.quiz ? lesson.quiz.length : 0}
          onMore={moreQuestions} onReplay={replayLesson} onClose={closeLesson} onShare={copyShareLink} mobile={mobile} />
      )}

      <Toast msg={toast} />

      {/* hover tooltip */}
      {hoverNode && !selNode && (
        <div style={{ position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 14,
          display: 'flex', alignItems: 'center', gap: 8, padding: '7px 13px', borderRadius: 99,
          background: 'rgba(8,11,18,0.82)', color: '#FFFFFF', fontSize: 12.5, fontWeight: 700, backdropFilter: 'blur(8px)',
          border: '1px solid rgba(255,255,255,0.08)', pointerEvents: 'none' }}>
          <Dot color={PAL[hoverNode.category]} size={8} />{hoverNode.displayLabel || hoverNode.label}
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Appearance" />
        <TweakColor label="Accent" value={t.accent} options={['#3A66FF', '#12B5C9', '#7C5CFF', '#F0556B', '#1F9E63']} onChange={v => setTweak('accent', v)} />
        <TweakRadio label="Stage" value={t.stage} options={['deep blue', 'charcoal', 'clinical']} onChange={v => setTweak('stage', v)} />
        <TweakRadio label="Panel surface" value={t.surface} options={['frosted', 'solid']} onChange={v => setTweak('surface', v)} />
        <TweakSection label="Specimen" />
        <TweakToggle label="Idle auto-rotate" value={t.autorotate} onChange={v => setTweak('autorotate', v)} />
        <TweakSlider label="Specimen glow" value={t.glow} min={0.8} max={1.4} step={0.02} unit="×" onChange={v => setTweak('glow', v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

function NeonatalSliceView({ slice, layerOn, arterialMode, venousMode }) {
  const canvasRef = React.useRef(null);
  const displayRef = React.useRef(null);
  const atlasRef = React.useRef(null);
  const [status, setStatus] = React.useState('loading');
  const [error, setError] = React.useState('');
  const [hoverLabel, setHoverLabel] = React.useState('');
  const activeAxis = Math.max(0, slice.findIndex(v => v.on));
  const activeSlice = slice[activeAxis] || slice[0];
  const planeNames = ['Sagittal', 'Axial', 'Coronal'];

  React.useEffect(() => {
    let alive = true;
    window.NeonatalVolume.load().then(atlas => {
      if (!alive) return;
      atlasRef.current = atlas;
      setStatus('ready');
    }).catch(err => {
      if (!alive) return;
      setError(err.message || String(err)); setStatus('error');
    });
    return () => { alive = false; };
  }, []);

  React.useEffect(() => {
    if (status !== 'ready' || !canvasRef.current || !atlasRef.current) return;
    displayRef.current = window.NeonatalVolume.render(canvasRef.current, atlasRef.current,
      activeAxis, activeSlice.value, {
        cortex: !!layerOn.cortex,
        white_matter: !!layerOn.white_matter,
        ventricles: !!layerOn.ventricles,
        arterialTerritories: arterialMode,
        venousTerritories: venousMode,
      });
  }, [status, activeAxis, activeSlice.value, layerOn.cortex, layerOn.white_matter, layerOn.ventricles, arterialMode, venousMode]);

  const inspect = (event) => {
    if (!atlasRef.current || !displayRef.current) return;
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const imageAspect = canvas.width / canvas.height;
    const boxAspect = rect.width / rect.height;
    let shownW, shownH, left, top;
    if (boxAspect > imageAspect) { shownH = rect.height; shownW = shownH * imageAspect; left = (rect.width - shownW) / 2; top = 0; }
    else { shownW = rect.width; shownH = shownW / imageAspect; left = 0; top = (rect.height - shownH) / 2; }
    const px = event.clientX - rect.left - left, py = event.clientY - rect.top - top;
    if (px < 0 || py < 0 || px >= shownW || py >= shownH) { setHoverLabel(''); return; }
    const hit = window.NeonatalVolume.voxelAt(displayRef.current,
      px / shownW * canvas.width, py / shownH * canvas.height, atlasRef.current);
    setHoverLabel(hit && hit.label ? hit.label : '');
  };

  return (
    <div className="volume-slice-view" onMouseMove={inspect} onMouseLeave={() => setHoverLabel('')}>
      <canvas ref={canvasRef} className="volume-slice-canvas" aria-label={`${planeNames[activeAxis]} neonatal MRI atlas section`} />
      <div className="volume-slice-status">
        <strong>{planeNames[activeAxis]} · 40-week neonatal atlas</strong>
        <span>{status === 'loading' ? 'Loading volumetric MRI…' : status === 'error' ? error : `Position ${activeSlice.value}% · ${arterialMode ? 'neonatal arterial supply territories' : venousMode ? 'cerebral venous drainage territories' : 'dHCP T2 + aligned segmentation'}`}</span>
      </div>
      {hoverLabel && <div className="volume-slice-hover">{hoverLabel}</div>}
      <div className="volume-slice-key" aria-label={arterialMode ? 'Arterial territory color key' : venousMode ? 'Venous drainage territory color key' : 'Segmentation color key'}>
        {arterialMode ? <React.Fragment>
          <span><i style={{background:'#ef6f88'}} />ACA</span>
          <span><i style={{background:'#36a6c8'}} />MCA</span>
          <span><i style={{background:'#89bd57'}} />PCA</span>
          <span><i style={{background:'#edaa4c'}} />Lenticulostriate</span>
          <span><i style={{background:'#a875d2'}} />Anterior choroidal</span>
          <span><i style={{background:'#5967bd'}} />Vertebrobasilar</span>
        </React.Fragment> : venousMode ? <React.Fragment>
          <span><i style={{background:'#557fc2'}} />Superior sagittal</span>
          <span><i style={{background:'#49a1b8'}} />Superficial middle</span>
          <span><i style={{background:'#4da68f'}} />Labbé / transverse</span>
          <span><i style={{background:'#8a68b1'}} />Deep Galenic</span>
          <span><i style={{background:'#8b789f'}} />Posterior fossa</span>
        </React.Fragment> : <React.Fragment>
          <span><i style={{background:'#42beeb'}} />Ventricular CSF</span>
          <span><i style={{background:'#f4c95c'}} />Corpus callosum</span>
          <span><i style={{background:'#9779e0'}} />Thalamus</span>
          <span><i style={{background:'#ef706c'}} />Caudate</span>
          <span><i style={{background:'#4cb5a6'}} />Lentiform</span>
          <span><i style={{background:'#5bb279'}} />Hippocampus</span>
        </React.Fragment>}
      </div>
      <div className="volume-slice-orientation" aria-hidden="true">
        <span className="top">{activeAxis === 1 ? 'A' : 'S'}</span>
        <span className="bottom">{activeAxis === 1 ? 'P' : 'I'}</span>
        <span className="left">{activeAxis === 0 ? 'A' : 'R'}</span>
        <span className="right">{activeAxis === 0 ? 'P' : 'L'}</span>
      </div>
    </div>
  );
}

function SlicePanel({ value, onChange, onView, layerOn, onToggleLayer, arterialMode, onToggleArterialMode, venousMode, onToggleVenousMode, mobile }) {
  const [compact, setCompact] = React.useState(false);
  const names = ['Sagittal', 'Axial', 'Coronal'];
  const directions = ['Right ↔ Left', 'Inferior ↔ Superior', 'Posterior ↔ Anterior'];
  const update = (i, patch) => {
    onChange(value.map((v, j) => {
      if (j === i) return { ...v, ...patch };
      return patch.on === true ? { ...v, on: false } : v;
    }));
    if (mobile && patch.on === true) setCompact(true);
  };
  const active = value.some(v => v.on);
  const activeIndex = value.findIndex(v => v.on);
  if (mobile && compact && activeIndex >= 0) {
    const v = value[activeIndex];
    return (
      <div className="glass mobile-slice-compact" aria-label={`${names[activeIndex]} slice controls`}>
        <div className="mobile-slice-compact-title">
          <strong>{names[activeIndex]}</strong>
          <span className="mono">{v.value}%</span>
        </div>
        <input aria-label={`${names[activeIndex]} slice position`} type="range" min="-100" max="100" step="1" value={v.value}
          onChange={e => update(activeIndex, { value: Number(e.target.value) })} />
        <button onClick={() => setCompact(false)} title="Show all MRI controls" aria-label="Show all MRI controls"><Icon name="sliders" size={16} /></button>
        <button onClick={() => onChange(value.map(item => ({ ...item, on: false })))} title="Clear slice" aria-label="Clear slice"><Icon name="x" size={16} /></button>
      </div>
    );
  }
  return (
    <div className={`glass ${mobile ? 'mobile-slice-panel' : ''}`} style={{ position: 'absolute', right: mobile ? 8 : 16, bottom: mobile ? 64 : 16, zIndex: 18,
      width: mobile ? 'calc(100vw - 16px)' : 'min(470px, calc(100vw - 32px))', padding: '10px 13px', borderRadius: 14, color: 'var(--on-stage)',
      maxHeight: mobile ? '52dvh' : undefined, overflowY: mobile ? 'auto' : undefined }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginBottom: 7 }}>
        <div>
          <div className="eyebrow-light" style={{ fontSize: 10, fontWeight: 750, letterSpacing: '.1em' }}>ANATOMICAL SECTION</div>
          <div style={{ fontSize: 8.5, color: 'var(--on-stage-soft)', opacity: .78, marginTop: 2 }}>
            {arterialMode ? 'Neonatal arterial supply territories · aligned to dHCP' : venousMode ? 'Cerebral venous drainage territories · aligned to dHCP' : 'Real 40-week T2 MRI · aligned dHCP labels'}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 4 }}>
          {mobile && active && <button onClick={() => setCompact(true)} title="Collapse controls to see the MRI" aria-label="Collapse MRI controls"
            style={{ padding: '4px 7px', borderRadius: 7, border: '1px solid var(--accent)', background: 'var(--accent-soft)', color: 'var(--on-stage)', fontSize: 9.5 }}>
            <Icon name="chevDown" size={12} />
          </button>}
          {['sagittal', 'coronal', 'axial', 'three-quarter'].map(v => (
            <button key={v} onClick={() => onView(v)} title={`${v} view`}
              style={{ padding: '4px 7px', borderRadius: 7, border: '1px solid var(--glass-edge)', background: 'rgba(255,255,255,.07)', color: 'var(--on-stage-soft)', fontSize: 9.5, cursor: 'pointer', textTransform: 'capitalize' }}>
              {v === 'three-quarter' ? '3/4' : v.slice(0, 3)}
            </button>
          ))}
        </div>
      </div>
      {value.map((v, i) => (
        <div key={names[i]} style={{ display: 'grid', gridTemplateColumns: '82px 1fr 42px', gap: 8, alignItems: 'center', margin: '7px 0' }}>
          <label style={{ fontSize: 11.5, display: 'flex', gap: 6, alignItems: 'center' }}>
            <input type="checkbox" checked={v.on} onChange={e => update(i, { on: e.target.checked })} /> {names[i]}
          </label>
          <div>
            <input aria-label={`${names[i]} slice position`} type="range" min="-100" max="100" step="1" value={v.value} disabled={!v.on}
              onChange={e => update(i, { value: Number(e.target.value) })} style={{ width: '100%' }} />
            <div style={{ fontSize: 8.5, letterSpacing: '.03em', color: 'var(--on-stage-soft)', opacity: v.on ? .8 : .35, textAlign: 'center', marginTop: -3 }}>{directions[i]}</div>
          </div>
          <span className="mono" style={{ fontSize: 10.5, textAlign: 'right', opacity: v.on ? 1 : .45 }}>{v.on ? `${v.value}%` : '—'}</span>
        </div>
      ))}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6, marginTop: 9, paddingTop: 9, borderTop: '1px solid var(--glass-edge)' }}>
        {[
          ['cortex', 'Cortex'], ['white_matter', 'White matter'], ['ventricles', 'Ventricles']
        ].map(([cat, label]) => {
          const on = !!layerOn[cat];
          return <button key={cat} onClick={() => onToggleLayer(cat)} aria-pressed={on}
            title={`${on ? 'Remove' : 'Add'} ${label.toLowerCase()}`}
            style={{ padding: '6px 7px', borderRadius: 8, border: `1px solid ${on ? 'var(--accent)' : 'var(--glass-edge)'}`,
              background: on ? 'var(--accent-soft)' : 'rgba(255,255,255,.06)', color: 'var(--on-stage)',
              fontSize: 9.5, fontWeight: 700, cursor: 'pointer' }}>
            {on ? '−' : '+'} {label}
          </button>;
        })}
      </div>
      <button onClick={onToggleArterialMode} aria-pressed={arterialMode}
        title={`${arterialMode ? 'Remove' : 'Add'} cerebral arteries and show their neonatal supply territories`}
        style={{ width: '100%', marginTop: 7, padding: '8px 10px', borderRadius: 9,
          border: `1px solid ${arterialMode ? '#d84d64' : 'var(--glass-edge)'}`,
          background: arterialMode ? 'rgba(216,77,100,.16)' : 'rgba(255,255,255,.06)',
          color: 'var(--on-stage)', fontSize: 10.5, fontWeight: 750, cursor: 'pointer' }}>
        {arterialMode ? '− Remove cerebral arterial system' : '+ Add cerebral arterial system'}
      </button>
      {arterialMode && <div style={{ marginTop: 5, color: 'var(--on-stage-soft)', fontSize: 8.5, lineHeight: 1.35 }}>
        Sections show educational neonatal supply territories; individual borders and watershed zones vary.
      </div>}
      <button onClick={onToggleVenousMode} aria-pressed={venousMode}
        title={`${venousMode ? 'Remove' : 'Add'} superficial and deep cerebral veins and show drainage territories`}
        style={{ width: '100%', marginTop: 6, padding: '8px 10px', borderRadius: 9,
          border: `1px solid ${venousMode ? '#557fc2' : 'var(--glass-edge)'}`,
          background: venousMode ? 'rgba(85,127,194,.17)' : 'rgba(255,255,255,.06)',
          color: 'var(--on-stage)', fontSize: 10.5, fontWeight: 750, cursor: 'pointer' }}>
        {venousMode ? '− Remove cerebral venous system' : '+ Add cerebral venous system'}
      </button>
      {venousMode && <div style={{ marginTop: 5, color: 'var(--on-stage-soft)', fontSize: 8.5, lineHeight: 1.35 }}>
        Sections show educational drainage regions; cortical veins and sinus dominance vary normally.
      </div>}
      {active && <button onClick={() => onChange(value.map(v => ({ ...v, on: false })))}
        style={{ marginTop: 5, border: 0, background: 'transparent', color: 'var(--on-stage-soft)', fontSize: 11, cursor: 'pointer' }}>Clear slices</button>}
    </div>
  );
}

/* bottom-center cookie-consent popup (gates Firebase/Google Analytics) */
function ConsentBanner({ onAccept, onDecline }) {
  return (
    <div className="glass glass-top-hi pop" style={{
      position: 'absolute', bottom: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 50,
      width: 'min(560px, calc(100vw - 32px))', padding: '12px 14px',
      display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap',
    }}>
      <Icon name="info" size={16} style={{ color: 'var(--ink-faint)', flex: '0 0 auto' }} />
      <span style={{ flex: 1, minWidth: 220, fontSize: 12.5, lineHeight: 1.45, color: 'var(--ink-soft)' }}>
        We use Google&nbsp;Analytics to understand usage. Decline keeps it anonymous &amp; cookieless.
      </span>
      <div style={{ display: 'flex', gap: 8, flex: '0 0 auto' }}>
        <button onClick={onDecline} style={{
          padding: '8px 14px', borderRadius: 10, border: '1px solid var(--hair)', background: 'rgba(255,255,255,0.5)',
          color: 'var(--ink-soft)', fontFamily: 'var(--font)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
        }}>Decline</button>
        <button onClick={onAccept} style={{
          padding: '8px 16px', borderRadius: 10, border: '1px solid transparent', background: 'var(--accent)',
          color: '#fff', fontFamily: 'var(--font)', fontSize: 12.5, fontWeight: 700, cursor: 'pointer',
        }}>Accept</button>
      </div>
    </div>
  );
}

/* small toast confirmation (e.g. "Link copied") */
function Toast({ msg }) {
  if (!msg) return null;
  return (
    <div className="pop" style={{ position: 'absolute', top: 18, left: '50%', transform: 'translateX(-50%)', zIndex: 60,
      display: 'flex', alignItems: 'center', gap: 8, padding: '9px 15px', borderRadius: 99,
      background: 'rgba(8,11,18,0.82)', color: '#FFFFFF', fontSize: 12.5, fontWeight: 650, backdropFilter: 'blur(10px)',
      border: '1px solid rgba(255,255,255,0.1)' }}>
      <Icon name="checkCircle" size={14} style={{ color: '#5fd08a' }} /> {msg}
    </div>
  );
}

/* stage toolbar: spin toggle, share (link / poster), palette, about (mobile: zoom + about, top-right) */
function Legend({ groups, layerOn, onPoster, posterBusy, onCopyLink, onZoom, mobile, autorotate, onToggleSpin, vrSupported, vrActive, onEnterVR, onExitVR }) {
  const [cred, setCred] = React.useState(false);     // credits
  const [share, setShare] = React.useState(false);   // share menu (link / poster)
  const pill = {
    display: 'flex', alignItems: 'center', gap: 7, padding: '8px 13px', borderRadius: 99, border: '1px solid var(--glass-edge)',
    color: 'var(--ink-soft)', fontFamily: 'var(--font)', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
  };
  const credLink = {
    display: 'flex', alignItems: 'center', gap: 8, textDecoration: 'none', color: 'var(--ink-soft)',
    fontSize: 12, fontWeight: 600, padding: '6px 8px', borderRadius: 8, border: '1px solid var(--hair)', background: 'rgba(255,255,255,0.5)',
  };
  const shareItem = {
    display: 'flex', alignItems: 'center', gap: 9, width: '100%', textAlign: 'left', cursor: 'pointer',
    border: 'none', background: 'transparent', color: 'var(--ink-soft)', fontFamily: 'var(--font)',
    fontSize: 12.5, fontWeight: 600, padding: '9px 10px', borderRadius: 9,
  };
  const about = (
    <div className="glass pop scroll" style={{ padding: '14px 15px', width: 290, maxHeight: '70vh', overflowY: 'auto' }}>
      <div className="eyebrow" style={{ marginBottom: 9 }}>About</div>
      <p style={{ margin: '0 0 12px', fontSize: 12.5, lineHeight: 1.55, color: 'var(--ink-soft)' }}>
        The <b style={{ color: 'var(--ink)' }}>most detailed interactive 3D brain</b> available to date, and
        it's <b style={{ color: 'var(--ink)' }}>constantly growing</b>. Free for everyone.
      </p>
      <p style={{ margin: '0 0 12px', fontSize: 12.5, lineHeight: 1.55, color: 'var(--ink-soft)' }}>
        The <a href="https://www.reddit.com/r/neuro/comments/1tyfydj/the_lack_of_a_proper_brain_map_drove_me_nuts_when/" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--accent)', textDecoration: 'none', fontWeight: 600 }}>r/neuro community</a> gave
        it a warm welcome - that meant a lot.
      </p>
      <div className="eyebrow" style={{ marginBottom: 9 }}>Credits</div>
      <a href="https://github.com/itayinbarr" target="_blank" rel="noopener noreferrer" style={{ ...credLink, marginBottom: 10 }}><Icon name="github" size={14} /> Built by Itay Inbar</a>
      <p style={{ margin: 0, fontSize: 10.5, lineHeight: 1.5, color: 'var(--ink-faint)' }}>
        Initial 3D anatomy from Z-Anatomy, built on BodyParts3D / DBCLS. Deep nuclei,
        hypothalamus and white-matter tracts registered from open MRI atlases: CIT168 (Pauli&nbsp;et&nbsp;al.
        2018; Tyszka&nbsp;&amp;&nbsp;Pauli 2016), the Najdenovska&nbsp;et&nbsp;al. 2018 thalamic atlas,
        the Neudorfer&nbsp;et&nbsp;al. 2020 hypothalamic atlas, and HCP1065 tract templates. Approximate and
        educational - not for clinical use.
      </p>
      <div style={{ display: 'grid', gap: 7, marginTop: 12 }}>
        <a href="ATTRIBUTION.md" target="_blank" rel="noopener noreferrer" style={credLink}>Full credits &amp; anatomy licences</a>
        <a href="THIRD_PARTY_NOTICES.md" target="_blank" rel="noopener noreferrer" style={credLink}>Third-party software notices</a>
        <a href="LICENSE" target="_blank" rel="noopener noreferrer" style={credLink}>Complete distribution licence</a>
        <a href="https://github.com/nncceducation-cpu/brain-atlas-3d" target="_blank" rel="noopener noreferrer" style={credLink}><Icon name="github" size={14} /> Source code</a>
      </div>
      <p style={{ margin: '12px 0 0', paddingTop: 10, borderTop: '1px solid var(--glass-edge)', fontSize: 10.5, lineHeight: 1.5, color: 'var(--ink-faint)' }}>
        Developed by Khorshid Mohammad · <a href="https://sarnatnncc.ca/" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ink-soft)' }}>The Harvey Sarnat NNCC Care Program</a> · <a href="https://sarnatnncc.ca/innovation.html" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--ink-soft)' }}>Dr. Luis Bello-Espinosa Innovation in NNCC Lab</a>
      </p>
    </div>
  );

  // "Try in VR (beta)" pill - only rendered when the browser reports immersive-vr
  // support (i.e. a real headset/VR browser like the Quest browser), so it never
  // shows on a plain desktop. Sits in this bottom-right (mobile: top-right) row.
  const vrBtn = vrSupported ? (
    <button onClick={vrActive ? onExitVR : onEnterVR} className="glass"
      style={vrActive ? { ...pill, borderColor: 'var(--accent)', color: 'var(--accent)' } : pill}
      title={vrActive ? 'Exit immersive VR' : 'Enter immersive VR (beta) — walk around and pick the brain apart in your headset'}>
      <Icon name="headset" size={14} /> {vrActive ? 'Exit VR' : 'VR'}
      {!vrActive && <span style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.05em', textTransform: 'uppercase', padding: '2px 5px', borderRadius: 5, background: 'var(--accent)', color: '#fff' }}>beta</span>}
    </button>
  ) : null;

  // mobile: pinned top-right - zoom −/+ then About, with the panel opening downward
  if (mobile) {
    const zoomBtn = { width: 38, height: 38, display: 'grid', placeItems: 'center', borderRadius: 99, border: '1px solid var(--glass-edge)', color: 'var(--ink-soft)', cursor: 'pointer' };
    return (
      <div style={{ position: 'absolute', right: 8, top: 8, zIndex: 22, display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 8 }}>
        <div style={{ display: 'flex', gap: 8 }}>
          {vrBtn}
          <a href="download.html"
            className="glass" style={{ ...zoomBtn, textDecoration: 'none' }} title="Download the offline desktop application" aria-label="Download desktop application">
            <Icon name="download" size={16} />
          </a>
          <button onClick={onToggleSpin} className="glass"
            style={autorotate ? { ...zoomBtn, borderColor: 'var(--accent)', color: 'var(--accent)' } : zoomBtn}
            title={autorotate ? 'Pause automatic brain rotation' : 'Resume automatic brain rotation'}
            aria-label={autorotate ? 'Pause brain rotation' : 'Resume brain rotation'}>
            <Icon name="rotate" size={16} />
          </button>
          <button onClick={() => onZoom(-1)} className="glass" style={zoomBtn} title="Zoom in"><Icon name="plus" size={16} /></button>
          <button onClick={() => onZoom(1)} className="glass" style={zoomBtn} title="Zoom out"><Icon name="minus" size={16} /></button>
          <button onClick={() => setCred(c => !c)} className="glass" style={pill}><Icon name="info" size={14} /> About</button>
        </div>
        {cred && about}
      </div>
    );
  }

  const spinPill = autorotate ? { ...pill, borderColor: 'var(--accent)', color: 'var(--accent)' } : pill;
  return (
    <div style={{ position: 'absolute', left: 16, top: 16, zIndex: 24, display: 'flex', flexDirection: 'column-reverse', alignItems: 'flex-start', gap: 8 }}>
      {cred && about}
      {share && !mobile && (
        <div className="glass pop" style={{ padding: 8, width: 232 }}>
          <button onClick={() => { onCopyLink(); setShare(false); }} style={shareItem} title="Copy a link that reopens this exact view">
            <Icon name="link" size={15} style={{ color: 'var(--ink-faint)' }} /> <span style={{ flex: 1 }}>Copy link to this view</span>
          </button>
          <button onClick={() => { onPoster(); setShare(false); }} disabled={posterBusy} style={{ ...shareItem, opacity: posterBusy ? 0.6 : 1 }} title="Download a high-definition poster (features the selected structure)">
            <Icon name={posterBusy ? 'reset' : 'download'} size={15} style={{ color: 'var(--ink-faint)' }} /> <span style={{ flex: 1 }}>{posterBusy ? 'Rendering\u2026' : 'Download poster'}</span>
          </button>
        </div>
      )}
      <div style={{ display: 'flex', gap: 8 }}>
        {vrBtn}
        <a href="download.html"
          className="glass" style={{ ...pill, textDecoration: 'none' }} title="Download the offline application for Windows or macOS">
          <Icon name="download" size={14} /> Desktop app
        </a>
        {!mobile && (
          <button onClick={() => { setShare(s => !s); setCred(false); }} className="glass" style={pill} title="Share this view: copy a link or download a poster">
            <Icon name="share" size={14} /> Share
          </button>
        )}
        <button onClick={() => { setCred(c => !c); setShare(false); }} className="glass" style={pill} title="About this project · credits & licence">
          <Icon name="info" size={14} /> About
        </button>
        {!mobile && (
          <button onClick={onToggleSpin} className="glass" style={spinPill}
            title={autorotate ? 'Pause automatic brain rotation' : 'Resume automatic brain rotation'}
            aria-label={autorotate ? 'Pause brain rotation' : 'Resume brain rotation'}>
            <Icon name="rotate" size={14} /> {autorotate ? 'Pause rotation' : 'Resume rotation'}
          </button>
        )}
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
