Psychology Before Procedures™
People Before Paperwork™
HAVEN University™ · A Safety Sense Inc. Education Division

HAVEN University

Human · Awareness · Vulnerability · Engagement · Neuroscience

Where the neuroscience of safety becomes a professional credential. Not borrowed theory. Not repackaged HOP. The real neuroscience, from the person who built it from 50+ investigations — now available as self-paced certifications and the Safety Disruptor™ designation.

scroll
10
Proprietary Frameworks in Curriculum
8
Certification Thresholds
30+
Years of Field Experience Behind Every Module
50+
Investigations as Source Material
HAVEN
Human · Awareness · Vulnerability · Engagement · Neuroscience

Every safety certification on the market teaches you what the literature says. HAVEN University™ teaches you what 50 investigations, 30 years, and 10 proprietary frameworks have actually revealed about why the brain fails in high-hazard environments — and what you can do about it.

The curriculum is vertically integrated with the Safety Sense Inc. framework ecosystem. Every course maps to a specific framework. Every certification builds on the previous. The Safety Disruptor™ designation is the capstone — representing mastery of the complete 10-framework system.

This is not borrowed theory. Not repackaged HOP. The real neuroscience, from the person who built it from 50+ investigations.

🧠
Vertically Integrated Curriculum
Each certification builds on the previous one. PERSONA Practitioner → NISOS Observer → Safety Disruptor™. No modules exist in isolation.
🎓
Self-Paced + Cohort Options
Complete certifications at your pace, or join a cohort for peer learning and live facilitation by Janel or certified HAVEN Faculty.
🏆
Safety Sense Inc. Owned Credentials
All certifications are proprietary to Safety Sense Inc. and recognized within the HAVEN University™ ecosystem and The Disrupter's Den community.
Certification Tracks

Three Paths to
Professional Designation

Each certification track is self-paced with optional cohort sessions. Prerequisites noted. All certifications owned by Safety Sense Inc.

Track 01 · Practitioner Level · Entry
PERSONA Practitioner Certification™

The entry point into the Safety Sense Inc. ecosystem. Master the 12 PERSONA archetypes, learn to profile individuals and teams, and develop the psychological vocabulary that makes every other framework accessible. Includes PERSONA SIF™ precursor identification training.

PERSONA™ Framework PERSONA SIF™ Intro Archetype Profiling Assessment & Exam
Self-paced · 6–8 weeks
Enroll →
Track 02 · Advanced Level · Prerequisite: PERSONA Practitioner
NISOS Observer Certification™

Build on your PERSONA foundation with the full NISOS™ Neuroscience-Informed Safety Observation System. Master the O.B.S.E.R.V.E. protocol, conduct psychologically-informed field observations, and integrate PRISM™ influence strategies for post-observation conversations.

NISOS™ Full Protocol PRISM™ Integration VOICE™ Paralinguistics Field Practicum
8–10 weeks + practicum
Enroll →
CAPSTONE DESIGNATION
Track 03 · Capstone · Professional Designation
Safety Disruptor™ Designation

The capstone of the HAVEN University™ curriculum. Integrate all 10 frameworks through the CRM™ meta-framework architecture. Develop a full organizational diagnostic capability. Complete a real-world implementation project. Graduate with the Safety Disruptor™ professional designation.

All 10 Frameworks CRM™ Mastery Org Diagnostic Practicum
Cohort required
Inquire →
The Learning Pathway

8 Thresholds to the
Safety Disruptor™ Designation

HAVEN University™ is structured as a progressive journey. Each Threshold unlocks the next — building a complete neuroscience safety practitioner one layer at a time. No shortcuts. No skipping rungs.

T1
Neuroscience Foundation
Why the brain fails in high-hazard environments
T2
PERSONA™ Archetype Mastery
12 archetypes · profiling · team dynamics
T3
NISOS™ Field Observer
O.B.S.E.R.V.E. protocol · field practicum
T4
VOICE™ Communication Science
Paralinguistics · tone · autonomic cues
T5
PRISM™ + BEACON™ Integration
Influence science · engagement systems
T6
Vagal Ladder™ + Backpack™
Autonomic states · ancestral scripts
T7
Quantum Safety™ + PERSONA SIF™
Observer effect · SIF precursor detection
T8
Safety Disruptor™ Designation
CRM™ mastery · org diagnostic practicum
Capstone
Thresholds 1–2
Foundation Tier
The neurological "why" behind safety behavior, plus the psychological vocabulary needed to understand every person on your site. Culminates in PERSONA Practitioner™ certification.
Thresholds 3–5
Practitioner Tier
Field-applied observation, communication science, and influence frameworks. You learn to see what others miss and have conversations that actually change behavior. Culminates in NISOS Observer™ certification.
Thresholds 6–8
Disruptor Tier
Deep psychology, quantum observer effects, SIF precursor analysis, and full CRM™ architecture mastery. You graduate capable of diagnosing and transforming entire safety cultures. Culminates in Safety Disruptor™ designation.
The Community

The Disrupter's Den

The Disrupter's Den on Skool is where HAVEN University™ graduates and serious safety practitioners go between certifications. Access to Janel's ongoing research, framework updates, peer case studies, and the community of safety professionals who are done doing safety the old way.

Ongoing framework application Q&A with Janel
Peer case study library (anonymized)
First access to new frameworks and research
Monthly live facilitation sessions
Join The Disrupter's Den →
The Disrupter's
Den
Powered by Skool
Where safety professionals who are done doing safety the old way come to learn, share, and disrupt.
Course Catalog

Framework Certifications &
Individual Courses

Each course is built around a single proprietary framework. Complete them individually to earn CEUs, or stack them toward a full certification track. Every course is self-paced with direct application tools.

Start Here · Free
HAVEN Orientation: The Neuroscience of Safety
An introduction to the brain science behind safety behavior. Understand why procedures fail and why Psychology Before Procedures™ changes everything. No cost. No prerequisites.
Free Access No Prerequisites 2 hrs
Framework 01 · Track 01
PERSONA™ Practitioner Course
Master the 12 psychological safety archetypes. Learn to identify, profile, and adapt your safety communication to every personality type on your site — including the ones everyone else has given up on.
12 Archetypes Archetype Assessment Team Profiling
Framework 02 · Track 02
NISOS™ Observer Course
The full O.B.S.E.R.V.E. protocol for neuroscience-informed field observations. See behavioral indicators others miss. Conduct observations that produce insight instead of compliance theater.
O.B.S.E.R.V.E. Protocol Field Practicum Prereq: PERSONA
Framework 03 · Standalone
VOICE™ Paralinguistics Course
How tone, pace, prosody, and non-verbal cues shape the autonomic response of everyone listening to you. The neuroscience of why how you say it matters more than what you say.
Paralinguistic Science Voice Analysis Audio Exercises
Framework 04 · Standalone
PRISM™ Influence Strategist Course
Psychological Response & Influence Strategy Model. The ethical science of why workers comply, resist, or shut down — and how to stop triggering the defensive responses that shut conversations down.
Influence Mapping Resistance Analysis Ethics Framework
Framework 05 · Standalone
BEACON™ Engagement Course
Behavioral Engagement & Adaptive Communication. The framework that turns safety conversations from confrontation into genuine connection — and produces the behavioral data you actually need.
Engagement Design Adaptive Scripts Feedback Loops
Framework 06 · Standalone
Vagal Safety Ladder™ Course
Polyvagal theory mapped to real-world safety behavior. Understand the autonomic state — ventral, sympathetic, dorsal — driving every decision on your site, and learn to shift it intentionally.
Polyvagal Theory Autonomic States Co-regulation
Framework 07 · Standalone
The Backpack™ Analyst Course
Childhood scripts, formative experiences, and ancestral risk patterns that shape workplace risk behavior in adults. The framework that explains why the same message lands completely differently on every site.
Script Theory Risk Conditioning Case Studies
Framework 08 · Standalone
Quantum Safety™ Practitioner Course
The observer effect applied to safety leadership. How the presence, attention, and energy of the observer changes the behavior being observed — and why this is the most underrated variable in safety culture.
Observer Effect Superposition & Safety Culture Calibration
Framework 10 · Applied
PERSONA SIF™ Analyst Course
Apply the PERSONA archetype system specifically to Serious Injury and Fatality precursor detection. Identify the psychological and behavioral indicators that appear before catastrophic events — and intervene earlier.
SIF Precursors 50+ Investigation Data Prereq: PERSONA
Framework 09 · Meta
CRM™ Architecture Course
The Cognitive Reliability Meta-Framework that integrates all 10 frameworks into a unified diagnostic and intervention system. The closest thing to a complete operating system for safety culture that exists.
Meta-Integration Org Diagnostics Prereq: Tracks 1–2
Proprietary IP Ecosystem

The Only Complete
Neuroscience Safety System

10 interconnected frameworks. One operating philosophy. Built from 30 years of field experience and 50+ fatality investigations — not from a textbook.

Framework 01
PERSONA™
12 psychological safety archetypes that reveal why each worker makes the decisions they do — and how to reach each one differently.
Framework 02
NISOS™
The O.B.S.E.R.V.E. protocol for neuroscience-informed observation. See what others miss. Understand what behavior is actually communicating.
Framework 03
VOICE™
Paralinguistic science applied to safety leadership. How you say it matters more than what you say — and the neuroscience proves it.
Framework 04
PRISM™
Psychological Response & Influence Strategy Model. The science of why people comply, resist, or shut down — and how to use it ethically.
Framework 05
BEACON™
Behavioral Engagement & Adaptive Communication. The framework that turns safety conversations from confrontation into connection.
Framework 06
Vagal Safety Ladder™
Polyvagal theory mapped to real-world safety behavior. Understand the autonomic state driving every decision on your site.
Ready to Disrupt?

Stop treating symptoms.
Start rewiring the system.

Every fatality I've investigated had warning signs in the psychology — not the procedure. Let's find them before they find your workers.

// ── Ecosystem Canvas (preserved exactly) (function(){ const canvas = document.getElementById('ecoCanvas'); if(!canvas) return; const ctx = canvas.getContext('2d'); let W, H, frame = 0, hoveredId = null; const NODES = [ { id:'PERSONA™', url:'/persona/', x:0.50, y:0.13, color:'#0E7C86', r:18, label:'12 Archetypes' }, { id:'NISOS™', url:'/nisos/', x:0.14, y:0.42, color:'#2563eb', r:15, label:'Observation' }, { id:'VOICE™', url:'/voice/', x:0.86, y:0.42, color:'#7c3aed', r:15, label:'Communication' }, { id:'PRISM™', url:'/prism/', x:0.24, y:0.82, color:'#06b6d4', r:15, label:'Influence' }, { id:'BEACON™', url:'/beacon/', x:0.76, y:0.82, color:'#E05C1A', r:15, label:'Engagement' }, { id:'Vagal Ladder™', url:'/vagal-safety-ladder/', x:0.10, y:0.20, color:'#9333ea', r:13, label:'Nervous System' }, { id:'Backpack™', url:'/the-backpack/', x:0.90, y:0.20, color:'#D4712A', r:13, label:'Ancestral Scripts' }, { id:'Quantum Safety™',url:'/quantum-safety/', x:0.50, y:0.88, color:'#C8952A', r:13, label:'Observer Effect' }, ]; const HUB = { id:'CRM™', url:'/crm/', x:0.50, y:0.50, color:'#C8952A', r:26, label:'Meta-Framework', isHub:true }; const SPOKE_PAIRS = [ [0,1],[0,2],[0,6],[0,7], [1,5],[2,6],[3,4],[5,6], [1,3],[2,4],[3,7],[4,7], ]; const PULSES = []; const RIPPLES = []; const DENDRITES = []; for(let i = 0; i < 55; i++){ DENDRITES.push({ x: Math.random(), y: Math.random(), vx:(Math.random()-0.5)*0.0006, vy:(Math.random()-0.5)*0.0006, r: Math.random()*1.5+0.4, o: Math.random()*0.18+0.05 }); } const STREAM_PATHS = []; let streamOffset = 0; function computeStreams(cx, cy){ STREAM_PATHS.length = 0; const N = 22; for(let s = 0; s < N; s++){ const path = []; const startAngle = (s/N)*Math.PI*2 + (Math.random()-0.5)*0.4; const startR = 0.44 + Math.random()*0.06; let x = cx + Math.cos(startAngle)*W*startR; let y = cy + Math.sin(startAngle)*H*startR*0.68; const rotDir = s%2===0 ? 1 : -1; for(let step = 0; step < 72; step++){ path.push({x,y}); const dx = cx-x, dy = cy-y; const dist = Math.sqrt(dx*dx+dy*dy); if(dist < 28) break; const base = Math.atan2(dy,dx); const rot = rotDir * 0.28 * Math.max(0, 1 - dist/(Math.min(W,H)*0.5)); const noiseA = Math.sin(step*0.18+s*1.3)*0.18; const angle = base + rot + noiseA; const speed = 6 + dist*0.028; x += Math.cos(angle)*speed; y += Math.sin(angle)*speed; } if(path.length > 4) STREAM_PATHS.push({ pts:path, col:['#0E7C86','#2563eb','#8B6BBF','#C8952A'][s%4] }); } } function drawBrain(cx,cy){ const bW=W*0.88,bH=H*0.88; ctx.save(); ctx.globalAlpha=0.05;ctx.fillStyle='#0E7C86'; ctx.beginPath();ctx.moveTo(cx,cy-bH*0.47); ctx.bezierCurveTo(cx-bW*0.10,cy-bH*0.50,cx-bW*0.28,cy-bH*0.49,cx-bW*0.38,cy-bH*0.42); ctx.bezierCurveTo(cx-bW*0.44,cy-bH*0.33,cx-bW*0.47,cy-bH*0.20,cx-bW*0.48,cy-bH*0.06); ctx.bezierCurveTo(cx-bW*0.50,cy+bH*0.06,cx-bW*0.51,cy+bH*0.18,cx-bW*0.49,cy+bH*0.28); ctx.bezierCurveTo(cx-bW*0.44,cy+bH*0.38,cx-bW*0.32,cy+bH*0.45,cx-bW*0.18,cy+bH*0.46); ctx.bezierCurveTo(cx-bW*0.08,cy+bH*0.46,cx,cy+bH*0.44,cx,cy+bH*0.44); ctx.bezierCurveTo(cx+bW*0.08,cy+bH*0.46,cx+bW*0.18,cy+bH*0.46,cx+bW*0.32,cy+bH*0.45); ctx.bezierCurveTo(cx+bW*0.44,cy+bH*0.38,cx+bW*0.49,cy+bH*0.28,cx+bW*0.51,cy+bH*0.18); ctx.bezierCurveTo(cx+bW*0.50,cy+bH*0.06,cx+bW*0.48,cy-bH*0.06,cx+bW*0.47,cy-bH*0.20); ctx.bezierCurveTo(cx+bW*0.44,cy-bH*0.33,cx+bW*0.38,cy-bH*0.42,cx+bW*0.28,cy-bH*0.49); ctx.bezierCurveTo(cx+bW*0.10,cy-bH*0.50,cx,cy-bH*0.47,cx,cy-bH*0.47); ctx.closePath();ctx.fill(); ctx.strokeStyle='rgba(14,124,134,1)'; ctx.lineWidth=2;ctx.globalAlpha=0.32; ctx.beginPath();ctx.moveTo(cx,cy-bH*0.47); ctx.bezierCurveTo(cx-bW*0.10,cy-bH*0.50,cx-bW*0.25,cy-bH*0.49,cx-bW*0.36,cy-bH*0.44); ctx.bezierCurveTo(cx-bW*0.43,cy-bH*0.38,cx-bW*0.47,cy-bH*0.28,cx-bW*0.48,cy-bH*0.14); ctx.bezierCurveTo(cx-bW*0.50,cy-bH*0.02,cx-bW*0.52,cy+bH*0.10,cx-bW*0.51,cy+bH*0.20); ctx.bezierCurveTo(cx-bW*0.50,cy+bH*0.30,cx-bW*0.45,cy+bH*0.40,cx-bW*0.34,cy+bH*0.46); ctx.bezierCurveTo(cx-bW*0.22,cy+bH*0.49,cx-bW*0.10,cy+bH*0.47,cx,cy+bH*0.44); ctx.bezierCurveTo(cx+bW*0.10,cy+bH*0.47,cx+bW*0.22,cy+bH*0.49,cx+bW*0.34,cy+bH*0.46); ctx.bezierCurveTo(cx+bW*0.45,cy+bH*0.40,cx+bW*0.50,cy+bH*0.30,cx+bW*0.51,cy+bH*0.20); ctx.bezierCurveTo(cx+bW*0.52,cy+bH*0.10,cx+bW*0.50,cy-bH*0.02,cx+bW*0.48,cy-bH*0.14); ctx.bezierCurveTo(cx+bW*0.47,cy-bH*0.28,cx+bW*0.43,cy-bH*0.38,cx+bW*0.36,cy-bH*0.44); ctx.bezierCurveTo(cx+bW*0.25,cy-bH*0.49,cx+bW*0.10,cy-bH*0.50,cx,cy-bH*0.47); ctx.closePath();ctx.stroke(); ctx.lineWidth=2;ctx.globalAlpha=0.38; ctx.beginPath();ctx.moveTo(cx,cy-bH*0.46); ctx.bezierCurveTo(cx+bW*0.014,cy-bH*0.25,cx-bW*0.014,cy+bH*0.06,cx+bW*0.005,cy+bH*0.28); ctx.bezierCurveTo(cx-bW*0.005,cy+bH*0.36,cx,cy+bH*0.43,cx,cy+bH*0.43); ctx.stroke(); ctx.lineWidth=2.2;ctx.globalAlpha=0.32; ctx.beginPath();ctx.moveTo(cx-bW*0.51,cy+bH*0.04); ctx.bezierCurveTo(cx-bW*0.42,cy-bH*0.02,cx-bW*0.32,cy-bH*0.06,cx-bW*0.20,cy-bH*0.07); ctx.bezierCurveTo(cx-bW*0.12,cy-bH*0.07,cx-bW*0.06,cy-bH*0.05,cx-bW*0.01,cy-bH*0.02); ctx.stroke(); ctx.beginPath();ctx.moveTo(cx+bW*0.51,cy+bH*0.04); ctx.bezierCurveTo(cx+bW*0.42,cy-bH*0.02,cx+bW*0.32,cy-bH*0.06,cx+bW*0.20,cy-bH*0.07); ctx.bezierCurveTo(cx+bW*0.12,cy-bH*0.07,cx+bW*0.06,cy-bH*0.05,cx+bW*0.01,cy-bH*0.02); ctx.stroke(); ctx.lineWidth=1.8;ctx.globalAlpha=0.28; ctx.beginPath();ctx.moveTo(cx-bW*0.46,cy-bH*0.32); ctx.bezierCurveTo(cx-bW*0.36,cy-bH*0.20,cx-bW*0.24,cy-bH*0.12,cx-bW*0.06,cy-bH*0.08);ctx.stroke(); ctx.beginPath();ctx.moveTo(cx+bW*0.46,cy-bH*0.32); ctx.bezierCurveTo(cx+bW*0.36,cy-bH*0.20,cx+bW*0.24,cy-bH*0.12,cx+bW*0.06,cy-bH*0.08);ctx.stroke(); ctx.lineWidth=1.1;ctx.globalAlpha=0.20; const gyri=[ [[-0.42,-0.44],[-0.32,-0.48],[-0.19,-0.48],[-0.08,-0.45]], [[-0.44,-0.37],[-0.34,-0.40],[-0.22,-0.40],[-0.10,-0.37]], [[-0.45,-0.28],[-0.36,-0.32],[-0.24,-0.32],[-0.10,-0.29]], [[-0.20,-0.47],[-0.22,-0.38],[-0.22,-0.28],[-0.20,-0.20]], [[-0.10,-0.45],[-0.11,-0.36],[-0.10,-0.26],[-0.08,-0.18]], [[-0.44,-0.18],[-0.35,-0.22],[-0.22,-0.22],[-0.07,-0.18]], [[-0.47,-0.06],[-0.37,-0.09],[-0.24,-0.09],[-0.08,-0.06]], [[-0.48, 0.06],[-0.37, 0.03],[-0.23, 0.02],[-0.07, 0.06]], [[-0.48, 0.18],[-0.37, 0.14],[-0.22, 0.14],[-0.07, 0.17]], [[-0.50,-0.20],[-0.50,-0.10],[-0.50,-0.00],[-0.49, 0.10]], [[-0.50, 0.12],[-0.48, 0.20],[-0.44, 0.28],[-0.36, 0.34]], [[-0.48, 0.30],[-0.42, 0.37],[-0.32, 0.42],[-0.20, 0.44]], [[-0.42, 0.34],[-0.32, 0.39],[-0.20, 0.42],[-0.08, 0.41]], [[-0.30, 0.44],[-0.20, 0.48],[-0.10, 0.48],[-0.03, 0.47]], [[-0.36,-0.44],[-0.38,-0.34],[-0.37,-0.24],[-0.34,-0.16]], [[-0.28,-0.47],[-0.30,-0.36],[-0.29,-0.26],[-0.26,-0.17]], ]; gyri.forEach(([s,c1,c2,e])=>{ ctx.beginPath();ctx.moveTo(cx+s[0]*bW,cy+s[1]*bH); ctx.bezierCurveTo(cx+c1[0]*bW,cy+c1[1]*bH,cx+c2[0]*bW,cy+c2[1]*bH,cx+e[0]*bW,cy+e[1]*bH);ctx.stroke(); ctx.beginPath();ctx.moveTo(cx-s[0]*bW,cy+s[1]*bH); ctx.bezierCurveTo(cx-c1[0]*bW,cy+c1[1]*bH,cx-c2[0]*bW,cy+c2[1]*bH,cx-e[0]*bW,cy+e[1]*bH);ctx.stroke(); }); ctx.globalAlpha=0.22;ctx.lineWidth=1.2; ctx.beginPath();ctx.arc(cx,cy+bH*0.38,bW*0.06,Math.PI*0.1,Math.PI*0.9,false);ctx.stroke(); ctx.restore(); } function drawStreamlines(){ if(!STREAM_PATHS.length) return; ctx.save(); streamOffset += 0.35; STREAM_PATHS.forEach((s, si) => { const pts = s.pts; if(pts.length < 3) return; ctx.beginPath();ctx.moveTo(pts[0].x, pts[0].y); for(let i=0; i0.055)return; const spoke=NODES[Math.floor(Math.random()*NODES.length)]; const toHub=Math.random()>0.5; const from=toHub?spoke:HUB,to=toHub?HUB:spoke,col=toHub?spoke.color:HUB.color; const cp=bezierCP(from.px,from.py,to.px,to.py,(toHub?1:-1)*0.22); PULSES.push({ax:from.px,ay:from.py,bx:to.px,by:to.py,cpx:cp.cx,cpy:cp.cy,t:0,speed:0.006+Math.random()*0.005,color:col,glow:true}); } function spawnSpokePulse(){ if(Math.random()>0.015)return; const pair=SPOKE_PAIRS[Math.floor(Math.random()*SPOKE_PAIRS.length)]; const a=NODES[pair[0]],b=NODES[pair[1]]; const cp=bezierCP(a.px,a.py,b.px,b.py,0.3); const toB=Math.random()>0.5,col=toB?a.color:b.color; PULSES.push({ax:a.px,ay:a.py,bx:b.px,by:b.py,cpx:cp.cx,cpy:cp.cy,t:toB?0:1,speed:(toB?1:-1)*(0.004+Math.random()*0.004),color:col,glow:false}); } function spawnRipple(){ if(Math.random()>0.007)return; RIPPLES.push({r:HUB.r+2,maxR:HUB.r*4.5,life:1,color:HUB.color}); } function drawAxon(ax,ay,bx,by,col,alpha=0.12,width=1,dashed=true){ const cp=bezierCP(ax,ay,bx,by,0.22); ctx.beginPath();ctx.moveTo(ax,ay);ctx.quadraticCurveTo(cp.cx,cp.cy,bx,by); ctx.strokeStyle=col+Math.round(alpha*255).toString(16).padStart(2,'0'); ctx.lineWidth=width; if(dashed){ctx.setLineDash([5,10]);} ctx.stroke();ctx.setLineDash([]); } function drawNode(n,isHovered,isHub){ const pulse=1+Math.sin(frame*0.022+n.x*8+n.y*5)*0.09; const r=n.r*(isHub?(1+Math.sin(frame*0.018)*0.06):pulse); const dimmed=hoveredId&&!isHovered&&n.id!==hoveredId; const alpha=dimmed?0.25:1; const glowR=r*(isHub?5:3.5); const grd=ctx.createRadialGradient(n.px,n.py,0,n.px,n.py,glowR); grd.addColorStop(0,n.color+(isHovered||isHub?'50':'28')); grd.addColorStop(1,n.color+'00'); ctx.globalAlpha=alpha;ctx.beginPath();ctx.arc(n.px,n.py,glowR,0,Math.PI*2);ctx.fillStyle=grd;ctx.fill(); if(isHovered){ctx.beginPath();ctx.arc(n.px,n.py,r+6,0,Math.PI*2);ctx.strokeStyle=n.color+'80';ctx.lineWidth=2;ctx.stroke();} const coreGrd=ctx.createRadialGradient(n.px,n.py,0,n.px,n.py,r); coreGrd.addColorStop(0,n.color+'ff');coreGrd.addColorStop(0.6,n.color+'cc');coreGrd.addColorStop(1,n.color+(isHub?'99':'66')); ctx.beginPath();ctx.arc(n.px,n.py,r,0,Math.PI*2);ctx.fillStyle=coreGrd;ctx.fill(); if(isHub||isHovered){ctx.beginPath();ctx.arc(n.px,n.py,r*0.38,0,Math.PI*2);ctx.fillStyle=`rgba(255,255,255,${isHub?0.55:0.4})`;ctx.fill();} ctx.globalAlpha=dimmed?0.2:(isHovered?1:0.75); ctx.font=`${isHub?'bold ':''} ${isHub?10:8.5}px 'JetBrains Mono',monospace`; ctx.fillStyle=isHovered?'#fff':'rgba(255,255,255,0.75)';ctx.textAlign='center';ctx.textBaseline='top'; ctx.shadowBlur=4;ctx.shadowColor='rgba(0,0,0,0.8)';ctx.fillText(n.id,n.px,n.py+r+6); ctx.font=`${isHub?8:7}px 'DM Sans',sans-serif`;ctx.fillStyle=n.color+(isHovered?'ff':'99'); ctx.fillText(n.label,n.px,n.py+r+18);ctx.shadowBlur=0;ctx.globalAlpha=1; } function drawTooltip(n){ const pad=14,w=140,h=48; let tx=n.px-w/2,ty=n.py-n.r-h-16; if(ty<8)ty=n.py+n.r+12; tx=Math.max(8,Math.min(W-w-8,tx)); ctx.fillStyle='rgba(8,15,26,0.92)'; ctx.beginPath();ctx.roundRect(tx,ty,w,h,8);ctx.fill(); ctx.strokeStyle=n.color+'66';ctx.lineWidth=1;ctx.stroke(); ctx.fillStyle=n.color;ctx.beginPath();ctx.roundRect(tx,ty,w,3,[8,8,0,0]);ctx.fill(); ctx.font='bold 9px JetBrains Mono,monospace';ctx.fillStyle='#fff';ctx.textAlign='left';ctx.textBaseline='top';ctx.fillText(n.id,tx+pad,ty+10); ctx.font='8px DM Sans,sans-serif';ctx.fillStyle='rgba(255,255,255,0.55)';ctx.fillText(n.label,tx+pad,ty+26); } function draw(){ if(!W||!H){ W=canvas.width=document.documentElement.clientWidth||window.innerWidth||900; H=canvas.height=500; requestAnimationFrame(draw); return; } ctx.clearRect(0,0,W,H);frame++; drawBrain(W/2,H/2);drawStreamlines(); DENDRITES.forEach(d=>{ d.x+=d.vx;d.y+=d.vy; if(d.x<0||d.x>1)d.vx*=-1;if(d.y<0||d.y>1)d.vy*=-1; ctx.beginPath();ctx.arc(d.x*W,d.y*H,d.r,0,Math.PI*2);ctx.fillStyle=`rgba(14,124,134,${d.o})`;ctx.fill(); }); NODES.forEach(n=>{ const isDim=hoveredId&&n.id!==hoveredId,isHov=n.id===hoveredId; drawAxon(HUB.px,HUB.py,n.px,n.py,n.color,isDim?0.04:(isHov?0.5:0.15),isHov?1.5:0.8,true); }); SPOKE_PAIRS.forEach(pair=>{ const a=NODES[pair[0]],b=NODES[pair[1]]; const isDim=hoveredId&&a.id!==hoveredId&&b.id!==hoveredId; drawAxon(a.px,a.py,b.px,b.py,'#ffffff',isDim?0.02:0.055,0.6,true); }); spawnRipple(); for(let i=RIPPLES.length-1;i>=0;i--){ const rp=RIPPLES[i];rp.r+=(rp.maxR-rp.r)*0.04;rp.life-=0.016; if(rp.life<=0){RIPPLES.splice(i,1);continue;} ctx.beginPath();ctx.arc(HUB.px,HUB.py,rp.r,0,Math.PI*2); ctx.strokeStyle=rp.color+Math.round(rp.life*80).toString(16).padStart(2,'0');ctx.lineWidth=1.5;ctx.stroke(); } spawnPulse();spawnSpokePulse(); for(let i=PULSES.length-1;i>=0;i--){ const p=PULSES[i];p.t+=p.speed; const done=p.speed>0?p.t>=1:p.t<=0;if(done){PULSES.splice(i,1);continue;} const t=Math.max(0,Math.min(1,p.t)),cp={cx:p.cpx,cy:p.cpy}; const pos=bezierPoint(p.ax,p.ay,cp.cx,cp.cy,p.bx,p.by,t); for(let tr=0;tr<4;tr++){ const tt=Math.max(0,t-tr*0.025*Math.abs(p.speed)/0.005); const tp=bezierPoint(p.ax,p.ay,cp.cx,cp.cy,p.bx,p.by,tt); ctx.beginPath();ctx.arc(tp.x,tp.y,2.5-tr*0.5,0,Math.PI*2);ctx.fillStyle=p.color; ctx.globalAlpha=Math.sin(t*Math.PI)*0.5*(1-tr*0.25);ctx.fill(); } ctx.globalAlpha=1; const grd=ctx.createRadialGradient(pos.x,pos.y,0,pos.x,pos.y,7); grd.addColorStop(0,p.color+'ff');grd.addColorStop(0.5,p.color+'88');grd.addColorStop(1,p.color+'00'); ctx.beginPath();ctx.arc(pos.x,pos.y,7,0,Math.PI*2);ctx.fillStyle=grd;ctx.globalAlpha=Math.sin(t*Math.PI)*0.9;ctx.fill();ctx.globalAlpha=1; } NODES.forEach(n=>drawNode(n,n.id===hoveredId,false)); drawNode(HUB,HUB.id===hoveredId,true); if(hoveredId){const hn=hoveredId===HUB.id?HUB:NODES.find(n=>n.id===hoveredId);if(hn)drawTooltip(hn);} requestAnimationFrame(draw); } function hitTest(e){ const rect=canvas.getBoundingClientRect();const mx=e.clientX-rect.left,my=e.clientY-rect.top; const all=[...NODES,HUB];for(const n of all){if(Math.hypot(mx-n.px,my-n.py){const n=hitTest(e);hoveredId=n?n.id:null;canvas.style.cursor=n?'pointer':'default';}); canvas.addEventListener('mouseleave',()=>{hoveredId=null;canvas.style.cursor='default';}); canvas.addEventListener('click',e=>{const n=hitTest(e);if(n&&n.url&&n.url!=='#')window.location.href=n.url;}); window.addEventListener('resize',resize); function startEco(){ var w = document.documentElement.clientWidth || window.innerWidth || 900; var h = 500; W = canvas.width = Math.floor(w); H = canvas.height = Math.floor(h); NODES.forEach(function(n){ n.px = n.x*W; n.py = n.y*H; }); HUB.px = HUB.x*W; HUB.py = HUB.y*H; computeStreams(HUB.px, HUB.py); if(!draw._running){ draw._running = true; draw(); } } setTimeout(startEco, 0); window.addEventListener('load', startEco); window.addEventListener('resize', function(){ draw._running = false; startEco(); }); })();