@@ -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+
29132function 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 >
0 commit comments