Skip to content

Commit 43d0915

Browse files
itayinbarrclaude
andcommitted
brain-atlas: add Systems & Learn modes wired to the real 3D specimen
Expand the atlas beyond structural Explore with two modes, bound to the actual bx_id meshes (not a static overlay): - Systems: step through 8 functional pathways (motor flagship) - structures glow on the specimen stage by stage, with a movable cinematic narration card. - Learn: lesson library (intro -> narrated stages -> quiz -> completion), including a "click the structure" question answered on the real brain. Also: - scene.js: setHighlight / clearHighlight / frameNodes for the pathway view. - Explore/Systems/Learn tabs (default Explore); panel auto-collapses when a system or lesson starts; Systems/Learn scroll with a bottom fade and match the Explore panel height. - Share control: copy a deep link (URL params) or download the poster. - SelectionCards cross-link into lessons featuring that structure. - Anatomy fixes: tighter cortical targeting (premotor, V1, Wernicke = planum temporale, ventral/dorsal visual streams), VTA shown at the substantia nigra with a note, CSF reabsorption at the superior sagittal sinus. - Removed the Amygdala & hypothalamus cinematic view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3b30899 commit 43d0915

10 files changed

Lines changed: 1098 additions & 21 deletions

File tree

brain-atlas/app.jsx

Lines changed: 231 additions & 12 deletions
Large diffs are not rendered by default.

brain-atlas/components.jsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ const ICONS = {
2929
download: '<path d="M12 3v12M7 10l5 5 5-5"/><path d="M5 21h14"/>',
3030
github: '<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/>',
3131
palette: '<rect x="3" y="3" width="7" height="7" rx="1.6"/><rect x="14" y="3" width="7" height="7" rx="1.6"/><rect x="14" y="14" width="7" height="7" rx="1.6"/><rect x="3" y="14" width="7" height="7" rx="1.6"/>',
32+
/* --- Systems & Lessons expansion --- */
33+
chevLeft: '<path d="m15 6-6 6 6 6"/>',
34+
play: '<path d="M6 4l14 8-14 8V4Z"/>',
35+
pause: '<path d="M7 4h3v16H7zM14 4h3v16h-3z"/>',
36+
skipBack: '<path d="M19 5v14L9 12l10-7ZM6 5v14"/>',
37+
route: '<circle cx="6" cy="19" r="3"/><circle cx="18" cy="5" r="3"/><path d="M9 19h5a4 4 0 0 0 4-4V9"/>',
38+
book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
39+
compass: '<circle cx="12" cy="12" r="9"/><path d="m16 8-2 6-6 2 2-6 6-2Z"/>',
40+
graduation: '<path d="M22 10 12 5 2 10l10 5 10-5Z"/><path d="M6 12v5c0 1 2.5 3 6 3s6-2 6-3v-5"/>',
41+
check: '<path d="M5 12l5 5L20 6"/>',
42+
checkCircle: '<circle cx="12" cy="12" r="9"/><path d="m8.5 12 2.5 2.5 4.5-5"/>',
43+
clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>',
44+
helpCircle: '<circle cx="12" cy="12" r="9"/><path d="M9.5 9a2.5 2.5 0 0 1 4.5 1.5c0 1.5-2 2-2 3M12 17h.01"/>',
45+
list: '<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>',
46+
grid: '<rect x="3" y="3" width="7" height="7" rx="1.4"/><rect x="14" y="3" width="7" height="7" rx="1.4"/><rect x="14" y="14" width="7" height="7" rx="1.4"/><rect x="3" y="14" width="7" height="7" rx="1.4"/>',
47+
rotate: '<path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"/>',
48+
link: '<path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1.5 1.5"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1.5-1.5"/>',
49+
share: '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.6 13.5 6.8 4M15.4 6.5 8.6 10.5"/>',
3250
};
3351

3452
function Icon({ name, size = 18, sw = 1.8, style, className }) {
@@ -39,16 +57,17 @@ function Icon({ name, size = 18, sw = 1.8, style, className }) {
3957
);
4058
}
4159

42-
/* round icon button */
43-
function IconBtn({ name, onClick, title, active, size = 16, dim = 30, sw = 1.8, style }) {
60+
/* round icon button (dark = sits on the cinematic dark-glass card) */
61+
function IconBtn({ name, onClick, title, active, size = 16, dim = 30, sw = 1.8, style, dark }) {
4462
const [h, setH] = React.useState(false);
63+
const hoverBg = dark ? 'rgba(255,255,255,0.10)' : 'rgba(16,20,32,0.05)';
4564
return (
4665
<button title={title} onClick={onClick} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
4766
style={{
4867
width: dim, height: dim, display: 'grid', placeItems: 'center', borderRadius: 9,
49-
border: '1px solid ' + (active ? 'transparent' : 'var(--hair)'),
50-
background: active ? 'var(--accent)' : (h ? 'rgba(16,20,32,0.05)' : 'transparent'),
51-
color: active ? '#fff' : 'var(--ink-soft)', transition: 'background .14s, color .14s, border-color .14s',
68+
border: '1px solid ' + (active ? 'transparent' : (dark ? 'rgba(255,255,255,0.14)' : 'var(--hair)')),
69+
background: active ? 'var(--accent)' : (h ? hoverBg : 'transparent'),
70+
color: active ? '#fff' : (dark ? 'var(--on-stage)' : 'var(--ink-soft)'), transition: 'background .14s, color .14s, border-color .14s',
5271
flex: '0 0 auto', ...style,
5372
}}>
5473
<Icon name={name} size={size} sw={sw} />

brain-atlas/control-panel.jsx

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,111 @@ function Slider({ value, min = 0, max = 1, step = 0.01, onChange, color, label,
2626
);
2727
}
2828

29+
const LEVEL_COLOR = { Intro: 'var(--c-ventricles)', Core: 'var(--accent)', Advanced: 'var(--c-deep_grey)' };
30+
31+
/* ---------------- Systems (functional views) ---------------- */
32+
function SystemsMode({ activeSystem, onStartSystem }) {
33+
return (
34+
<div style={{ padding: '0 10px 10px' }}>
35+
<p style={{ margin: '0 10px 12px', fontSize: 12, lineHeight: 1.5, color: 'var(--ink-faint)' }}>
36+
Watch a functional pathway light up on the specimen, stage by stage. Pick a system to step through it.
37+
</p>
38+
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
39+
{window.SYS.SYSTEMS.map(s => {
40+
const on = activeSystem === s.id;
41+
const accent = 'var(--c-' + (s.cat || 'cortex') + ')';
42+
return (
43+
<button key={s.id} onClick={() => onStartSystem(s.id)} style={{
44+
display: 'flex', alignItems: 'center', gap: 11, padding: '11px 12px', borderRadius: 12, textAlign: 'left', cursor: 'pointer',
45+
border: '1px solid ' + (on ? 'transparent' : 'var(--hair)'), background: on ? 'var(--ink)' : 'rgba(255,255,255,0.5)',
46+
color: on ? '#fff' : 'var(--ink)', transition: 'all .15s',
47+
}}>
48+
<span style={{ width: 26, height: 26, borderRadius: 8, flex: '0 0 auto', display: 'grid', placeItems: 'center',
49+
background: on ? 'rgba(255,255,255,0.14)' : accent + '33' }}>
50+
<span style={{ width: 9, height: 9, borderRadius: 99, background: accent, boxShadow: '0 0 8px ' + accent }} />
51+
</span>
52+
<span style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
53+
<span style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
54+
<span style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>{s.label}</span>
55+
{s.flagship && <span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', color: on ? '#fff' : 'var(--accent)', background: on ? 'rgba(255,255,255,0.16)' : 'var(--accent-soft)', padding: '1px 6px', borderRadius: 99 }}>Full</span>}
56+
</span>
57+
<span style={{ display: 'block', fontSize: 11.5, lineHeight: 1.4, color: on ? 'rgba(255,255,255,0.7)' : 'var(--ink-faint)', textWrap: 'pretty' }}>{s.blurb}</span>
58+
</span>
59+
<span style={{ flex: '0 0 auto', display: 'grid', placeItems: 'center', width: 28, height: 28, borderRadius: 99, background: on ? 'var(--accent)' : 'transparent', color: on ? '#fff' : 'var(--ink-ghost)' }}>
60+
<Icon name={on ? 'pause' : 'play'} size={13} />
61+
</span>
62+
</button>
63+
);
64+
})}
65+
</div>
66+
</div>
67+
);
68+
}
69+
70+
/* ---------------- Learn (lesson library) ---------------- */
71+
function LessonCard({ lesson, layout, completed, onOpen }) {
72+
const sys = window.SYS.SYSTEMS.find(s => s.id === lesson.system);
73+
const accent = sys ? 'var(--c-' + sys.cat + ')' : 'var(--accent)';
74+
const steps = sys ? sys.stages.length : 0;
75+
const lvlColor = LEVEL_COLOR[lesson.level] || 'var(--accent)';
76+
77+
if (layout === 'list') {
78+
return (
79+
<button onClick={onOpen} style={{ display: 'flex', alignItems: 'center', gap: 11, width: '100%', padding: '10px 11px', borderRadius: 11, textAlign: 'left', cursor: 'pointer', border: '1px solid var(--hair)', background: 'rgba(255,255,255,0.5)' }}>
80+
<span style={{ width: 30, height: 30, borderRadius: 8, flex: '0 0 auto', display: 'grid', placeItems: 'center', background: accent + '33' }}>
81+
{completed ? <Icon name="checkCircle" size={16} style={{ color: '#3aa564' }} /> : <span style={{ width: 9, height: 9, borderRadius: 99, background: accent }} />}
82+
</span>
83+
<span style={{ flex: 1, minWidth: 0 }}>
84+
<span style={{ display: 'block', fontSize: 13, fontWeight: 700, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{lesson.title}</span>
85+
<span className="mono" style={{ fontSize: 10, color: 'var(--ink-faint)' }}>{lesson.minutes} min · {steps} stages</span>
86+
</span>
87+
<Icon name="chevRight" size={15} style={{ color: 'var(--ink-ghost)', flex: '0 0 auto' }} />
88+
</button>
89+
);
90+
}
91+
return (
92+
<button onClick={onOpen} style={{ display: 'block', width: '100%', padding: '13px 14px 14px', borderRadius: 14, textAlign: 'left', cursor: 'pointer', border: '1px solid var(--hair)', background: 'rgba(255,255,255,0.55)', position: 'relative', overflow: 'hidden' }}>
93+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 9 }}>
94+
<span className="eyebrow" style={{ color: accent, letterSpacing: '0.1em' }}>{lesson.kicker}</span>
95+
<span style={{ flex: 1 }} />
96+
{lesson.flagship && <span style={{ fontSize: 9, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--accent)', background: 'var(--accent-soft)', padding: '2px 7px', borderRadius: 99 }}>Flagship</span>}
97+
{completed && <Icon name="checkCircle" size={16} style={{ color: '#3aa564' }} />}
98+
</div>
99+
<div style={{ fontSize: 15.5, fontWeight: 800, letterSpacing: '-0.015em', color: 'var(--ink)', lineHeight: 1.18, marginBottom: 5, textWrap: 'pretty' }}>{lesson.title}</div>
100+
<div style={{ fontSize: 12.5, lineHeight: 1.45, color: 'var(--ink-soft)', marginBottom: 11, textWrap: 'pretty' }}>{lesson.subtitle}</div>
101+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
102+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11, fontWeight: 600, color: lvlColor, background: 'rgba(16,20,32,0.04)', padding: '3px 9px', borderRadius: 99 }}>
103+
<span style={{ width: 6, height: 6, borderRadius: 99, background: lvlColor }} />{lesson.level}
104+
</span>
105+
<span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-faint)', display: 'inline-flex', alignItems: 'center', gap: 4 }}><Icon name="clock" size={11} />{lesson.minutes} min</span>
106+
<span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-faint)' }}>· {steps} stages</span>
107+
</div>
108+
</button>
109+
);
110+
}
111+
112+
function LearnMode({ completedSet, onOpenLesson }) {
113+
const [layout, setLayout] = React.useState('cards');
114+
const LESSONS = window.SYS.LESSONS;
115+
const done = LESSONS.filter(l => completedSet.has(l.id)).length;
116+
return (
117+
<div style={{ padding: '0 12px 12px' }}>
118+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
119+
<span className="eyebrow" style={{ flex: 1 }}>{done} of {LESSONS.length} completed</span>
120+
<Segmented style={{ width: 70 }} value={layout} onChange={setLayout}
121+
options={[{ value: 'cards', icon: 'grid', label: '' }, { value: 'list', icon: 'list', label: '' }]} />
122+
</div>
123+
<div style={{ display: 'flex', flexDirection: 'column', gap: layout === 'list' ? 7 : 10 }}>
124+
{LESSONS.map(l => (
125+
<LessonCard key={l.id} lesson={l} layout={layout} completed={completedSet.has(l.id)} onOpen={() => onOpenLesson(l.id)} />
126+
))}
127+
</div>
128+
</div>
129+
);
130+
}
131+
29132
function ControlPanel(props) {
30-
const { pos, setPos, collapsed, setCollapsed } = props;
133+
const { pos, setPos, collapsed, setCollapsed, mode, setMode } = props;
31134
const dragRef = React.useRef(null);
32135
const [layersOpen, setLayersOpen] = React.useState(false);
33136
const [viewsOpen, setViewsOpen] = React.useState(true); // collapse presets to free space for results
@@ -55,7 +158,9 @@ function ControlPanel(props) {
55158
if (mobile && props.selectedId != null) return null;
56159
const containerStyle = mobile
57160
? { position: 'absolute', left: 8, right: 8, bottom: 8, maxHeight: '44vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', zIndex: 20 }
58-
: { position: 'absolute', left: pos.x, top: pos.y, width: 326, maxHeight: 'calc(100vh - 32px)', display: 'flex', flexDirection: 'column', overflow: 'hidden', zIndex: 20 };
161+
: { position: 'absolute', left: pos.x, top: pos.y, width: 326,
162+
height: collapsed ? undefined : 'min(760px, calc(100vh - 32px))', maxHeight: 'calc(100vh - 32px)',
163+
display: 'flex', flexDirection: 'column', overflow: 'hidden', zIndex: 20 };
59164
return (
60165
<div className="glass glass-top-hi pop" style={containerStyle}>
61166
{/* header (hidden on mobile - bottom sheet shows only the cinematic views) */}
@@ -74,6 +179,28 @@ function ControlPanel(props) {
74179

75180
{!collapsed && (
76181
<React.Fragment>
182+
{/* mode tabs - Explore · Systems · Learn */}
183+
<div style={{ padding: mobile ? '12px 12px 10px' : '0 12px 12px' }}>
184+
<Segmented value={mode} onChange={setMode}
185+
options={[
186+
{ value: 'explore', label: 'Explore', icon: 'compass' },
187+
{ value: 'systems', label: 'Systems', icon: 'route' },
188+
{ value: 'learn', label: 'Learn', icon: 'graduation' },
189+
]} />
190+
</div>
191+
192+
{(mode === 'systems' || mode === 'learn') && (
193+
<div style={{ position: 'relative', flex: 1, minHeight: 0, display: 'flex' }}>
194+
<div className="scroll" style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
195+
{mode === 'systems' && <SystemsMode activeSystem={props.activeSystem} onStartSystem={props.onStartSystem} />}
196+
{mode === 'learn' && <LearnMode completedSet={props.completedSet} onOpenLesson={props.onOpenLesson} />}
197+
</div>
198+
<div className="panel-fade" />
199+
</div>
200+
)}
201+
202+
{mode === 'explore' && (
203+
<React.Fragment>
77204
{/* search */}
78205
{!mobile && (
79206
<div style={{ padding: '0 12px 12px' }}>
@@ -163,6 +290,9 @@ function ControlPanel(props) {
163290
</div>
164291
)}
165292

293+
{/* spacer so the footer sits at the bottom (matches Systems/Learn height) */}
294+
{!mobile && !layersShown && <div style={{ flex: 1, minHeight: 0 }} />}
295+
166296
{/* footer */}
167297
{!mobile && (
168298
<div style={{ display: 'flex', gap: 8, padding: '10px 12px', borderTop: '1px solid var(--hair-2)' }}>
@@ -172,6 +302,8 @@ function ControlPanel(props) {
172302
: <button onClick={props.onIsolateMatches} disabled={!props.canIsolate} style={{ ...footBtn, opacity: props.canIsolate ? 1 : 0.4 }}><Icon name="isolate" size={14} /> Isolate</button>}
173303
</div>
174304
)}
305+
</React.Fragment>
306+
)}
175307
</React.Fragment>
176308
)}
177309
</div>

brain-atlas/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272

7373
<!-- data + scene + analytics (plain JS) -->
7474
<script src="data.js"></script>
75+
<script src="systems-data.js"></script>
7576
<script src="scene.js"></script>
7677
<script src="firebase-analytics.js"></script>
7778

@@ -81,6 +82,8 @@
8182
<script type="text/babel" src="layers-tree.jsx"></script>
8283
<script type="text/babel" src="control-panel.jsx"></script>
8384
<script type="text/babel" src="selection-card.jsx"></script>
85+
<script type="text/babel" src="narration.jsx"></script>
86+
<script type="text/babel" src="lessons.jsx"></script>
8487
<script type="text/babel" src="app.jsx"></script>
8588
</body>
8689
</html>

0 commit comments

Comments
 (0)