// DepthMap Trait Architect - Core Logic // ------------------------------ // Data Model // ------------------------------ const TRAITS = [ { id: 'curiosity', name: 'Curiosity', description: 'Your drive to explore, ask questions, and seek novelty.', growthPaths: [ 'Schedule a weekly micro-adventure outside your routine.', 'Ask one “why” or “how” question before moving to solutions.', 'Follow a topic for 20 minutes/day until you see progress.' ], prompts: [ 'What topic have you always wanted to explore but never started?', 'What would change if you replaced one habitual task with a small experiment?' ] }, { id: 'discipline', name: 'Discipline', description: 'Your capacity to follow through with commitments despite mood or temptation.', growthPaths: [ 'Define a 5-minute “start ritual” for key habits.', 'Tie a friction to the undesired behavior and a reward to the desired one.', 'Use calendar blocking for your top 3 priorities.' ], prompts: [ 'Which small daily action would create the most compound progress?', 'What is the smallest unit of your goal you can commit to consistently?' ] }, { id: 'integrity', name: 'Integrity', description: 'Your alignment between values, decisions, and actions.', growthPaths: [ 'Write down your top 3 values and review weekly.', 'Say “no” to one misaligned request this week.', 'Make one promise today and keep it exactly as stated.' ], prompts: [ 'Where do you feel most authentic in your current routines?', 'What would living by your values look like in the next 30 days?' ] }, { id: 'boldness', name: 'Boldness', description: 'Your willingness to act decisively, take risks, and lead.', growthPaths: [ 'Pitch one idea in the next 48 hours.', 'Publicly commit to a small but meaningful milestone.', 'Have one uncomfortable conversation you’ve been avoiding.' ], prompts: [ 'What would you attempt if failure were safe and instructive?', 'Which decision, made today, would future you thank you for?' ] }, { id: 'empathy', name: 'Empathy', description: 'Your ability to understand, relate to, and care for others.', growthPaths: [ 'Practice “perspective labeling” in one conversation daily.', 'Ask “what matters to you?” before “what do you think?”', 'Write a thank-you note recognizing someone’s specific contribution.' ], prompts: [ 'Where are others likely feeling unseen?', 'How would you support your past self in your current situation?' ] }, { id: 'resilience', name: 'Resilience', description: 'Your capacity to recover, adapt, and grow from setbacks.', growthPaths: [ 'Design a “restart routine” for after tough days.', 'List three internal resources you can调用 in stress.', 'Celebrate one small recovery win daily.' ], prompts: [ 'What have you bounced back from that you’re proud of?', 'What would a resilient week look like if it started tomorrow?' ] }, { id: 'focus', name: 'Focus', description: 'Your ability to concentrate deeply and protect attention.', growthPaths: [ 'Choose a single task and timebox 25 minutes.', 'Turn off non-critical notifications for deep work blocks.', 'Create a visible “focus cue” to signal start and end.' ], prompts: [ 'What would you accomplish if you protected one hour daily for 7 days?', 'What pulls your attention most often, and how can you redesign that?' ] }, { id: 'creativity', name: 'Creativity', description: 'Your ability to generate novel, useful ideas and solutions.', growthPaths: [ 'Generate 10 rough ideas before judging any.', 'Recombine two unrelated concepts into a new idea.', 'Build a tiny prototype to test one idea this week.' ], prompts: [ 'What constraints, removed, would unlock new options for you?', 'How can you make space for “play” in your problem-solving?' ] } ]; const QUESTIONS = [ // Curiosity { id: 'q1', text: 'I enjoy exploring new topics for fun.', traitId: 'curiosity' }, { id: 'q2', text: 'I frequently seek sources beyond my usual information diet.', traitId: 'curiosity' }, // Discipline { id: 'q3', text: 'I follow through on commitments even when I don’t feel like it.', traitId: 'discipline' }, { id: 'q4', text: 'I prefer systems and routines that reduce decision fatigue.', traitId: 'discipline' }, // Integrity { id: 'q5', text: 'I act according to my values even when it’s inconvenient.', traitId: 'integrity' }, { id: 'q6', text: 'I keep promises to myself as seriously as to others.', traitId: 'integrity' }, // Boldness { id: 'q7', text: 'I am comfortable making decisions with incomplete information.', traitId: 'boldness' }, { id: 'q8', text: 'I volunteer to lead or take point when needed.', traitId: 'boldness' }, // Empathy { id: 'q9', text: 'I regularly consider others’ perspectives and emotions.', traitId: 'empathy' }, { id: 'q10', text: 'I make space for people to feel heard and understood.', traitId: 'empathy' }, // Resilience { id: 'q11', text: 'I recover quickly from setbacks without spiraling.', traitId: 'resilience' }, { id: 'q12', text: 'I can reframe challenges into learnings or experiments.', traitId: 'resilience' }, // Focus { id: 'q13', text: 'I can concentrate for extended periods without distraction.', traitId: 'focus' }, { id: 'q14', text: 'I protect my attention deliberately, especially during deep work.', traitId: 'focus' }, // Creativity { id: 'q15', text: 'I enjoy brainstorming even when ideas are rough or weird.', traitId: 'creativity' }, { id: 'q16', text: 'I connect unrelated ideas to form novel solutions.', traitId: 'creativity' } ]; const ANSWER_OPTIONS = [ { label: 'Not at all', value: 0 }, { label: 'Slightly', value: 1 }, { label: 'Moderately', value: 2 }, { label: 'Very', value: 3 }, { label: 'Extremely', value: 4 } ]; // ------------------------------ // State // ------------------------------ const state = { step: 0, // hero: -1, test: 0..15, results: 16+ answers: new Array(QUESTIONS.length).fill(null), scores: {}, // { traitId: 0..23 } selfBiasScore: 0, // 0..1 deepUnlocked: false }; // Elements const heroEl = document.getElementById('hero'); const testEl = document.getElementById('test'); const mapEl = document.getElementById('map'); const hexGridEl = document.getElementById('hexGrid'); const startBtn = document.getElementById('startBtn'); const loadDemoBtn = document.getElementById('loadDemoBtn'); const backToHeroBtn = document.getElementById('backToHero'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const progressEl = document.getElementById('progress'); const resetBtn = document.getElementById('resetBtn'); const retakeBtn = document.getElementById('retakeBtn'); const exportBtn = document.getElementById('exportBtn'); const panel = document.getElementById('panel'); const panelBackdrop = document.getElementById('panelBackdrop'); const panelContent = document.getElementById('panelContent'); const panelClose = document.getElementById('panelClose'); const panelTitle = document.getElementById('panelTitle'); const panelSubtitle = document.getElementById('panelSubtitle'); const panelDesc = document.getElementById('panelDesc'); const panelScore = document.getElementById('panelScore'); const panelScoreBar = document.getElementById('panelScoreBar'); const panelGrowth = document.getElementById('panelGrowth'); const panelPrompts = document.getElementById('panelPrompts'); const panelBadge = document.getElementById('panelBadge'); const panelUnlock = document.getElementById('panelUnlock'); const panelUnlockText = document.getElementById('panelUnlockText'); const insightBanner = document.getElementById('insightBanner'); const insightText = document.getElementById('insightText'); // ------------------------------ // Helpers // ------------------------------ const clamp = (v, min, max) => Math.min(max, Math.max(min, v)); const sum = (arr) => arr.reduce((a,b) => a+b, 0); function formatNumber(n, digits=0) { return Number(n).toFixed(digits); } function computeScores() { // Map answers (0..4) per question -> scaled to (0..23) for 2 questions per trait const traitTotals = {}; const traitCounts = {}; state.answers.forEach((val, idx) => { const q = QUESTIONS[idx]; const v = val ?? 0; traitTotals[q.traitId] = (traitTotals[q.traitId] ?? 0) + v; traitCounts[q.traitId] = (traitCounts[q.traitId] ?? 0) + 1; }); const scores = {}; for (const trait of TRAITS) { const avg = (traitTotals[trait.id] ?? 0) / (traitCounts[trait.id] || 1); scores[trait.id] = Math.round(avg * (23/4)); // 0..23 } state.scores = scores; } function computeSelfBias() { // Count responses 3 or 4 (Very/Extremely) const high = state.answers.filter(v => v === 3 || v === 4).length; state.selfBiasScore = (state.answers.length ? high / state.answers.length : 0); state.deepUnlocked = state.selfBiasScore < 0.6; // unlocked if <60% high responses } // HSL gradient by score: high=gold, mid=blue, low=desaturated function scoreToHSL(score) { const s = clamp(score / 23, 0, 1); let hue, sat, light; if (s >= 0.6) { // Gold tones (45..35) const t = (s - 0.6) / 0.4; hue = 45 - 10 * t; sat = 75 + 10 * t; // 75..85 light = 52 - 7 * t; // 52..45 } else if (s >= 0.3) { // Blues (210..230) const t = (s - 0.3) / 0.3; hue = 210 + 20 * t; // 210..230 sat = 70 + 5 * t; // 70..75 light = 52 + 3 * t; // 52..55 } else { // Desaturated blue-gray (210..220) const t = s / 0.3; hue = 210 + 10 * t; sat = 10 + 10 * t; // 10..20 light = 40 + 5 * t; // 40..45 } return { hue, sat, light, str: `hsl(${hue} ${sat}% ${light}%)` }; } function scoreLabel(score) { if (score >= 16) return 'High'; if (score >= 8) return 'Balanced'; return 'Emerging'; } function showSection(section) { heroEl.classList.add('hidden'); testEl.classList.add('hidden'); mapEl.classList.add('hidden'); if (section === 'hero') heroEl.classList.remove('hidden'); if (section === 'test') testEl.classList.remove('hidden'); if (section === 'map') mapEl.classList.remove('hidden'); } // ------------------------------ // Renderers // ------------------------------ function renderTest() { progressEl.textContent = `${Math.max(0, state.step + 1)} / ${QUESTIONS.length}`; const q = QUESTIONS[state.step]; const container = document.getElementById('questions'); container.innerHTML = ''; const block = document.createElement('div'); block.className = 'rounded-xl border border-white/10 bg-white/5 p-6'; const header = document.createElement('div'); header.className = 'flex items-start justify-between gap-3 mb-4'; header.innerHTML = `
Question ${state.step + 1} of ${QUESTIONS.length}

${q.text}

${TRAITS.find(t => t.id === q.traitId)?.name ?? ''}
`; block.appendChild(header); const options = document.createElement('div'); options.className = 'grid grid-cols-1 sm:grid-cols-2 gap-3 mt-3'; ANSWER_OPTIONS.forEach((opt, idx) => { const id = `opt-${state.step}-${idx}`; const checked = state.answers[state.step] === opt.value; const item = document.createElement('label'); item.setAttribute('for', id); item.className = `flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition ${checked ? 'border-gold-500/60 bg-gold-500/10' : 'border-white/10 bg-white/5 hover:bg-white/10'}`; item.innerHTML = `
${opt.label}
`; item.addEventListener('click', () => { state.answers[state.step] = opt.value; renderTest(); // re-render to reflect selection }); options.appendChild(item); }); block.appendChild(options); container.appendChild(block); // Controls prevBtn.disabled = state.step === 0; nextBtn.textContent = state.step === QUESTIONS.length - 1 ? 'Finish' : 'Next'; } function renderMap() { // Compute scores if not already computeScores(); computeSelfBias(); // Insight banner if (state.selfBiasScore >= 0.85) { insightBanner.classList.remove('hidden'); insightText.innerHTML = ` Self-Reflection Prompt: Your responses trend very high. Before unlocking deeper growth paths, consider: “Which areas feel genuinely strong right now, and which might I be overlooking or minimizing?” `; } else if (!state.deepUnlocked) { insightBanner.classList.remove('hidden'); insightText.textContent = `Your responses suggest sincere reflection. Keep exploring — deeper pathways will open soon.`; } else { insightBanner.classList.add('hidden'); } // Build hexes in a simple honeycomb layout (3 rows x 3 cols) // Adjust columns/rows responsively const cols = 3; const rows = 3; const colGap = 28; const rowGap = 24; // Dynamic hex size by container const containerWidth = hexGridEl.clientWidth || 800; const hexW = Math.min(160, Math.max(120, Math.floor(containerWidth / (cols + 0.5)))); const hexH = Math.floor(hexW * 1.15); const stepX = Math.floor(hexW * 0.75 + colGap); const stepY = Math.floor(hexH * 0.5 + rowGap); const width = (cols - 1) * stepX + hexW + 32; const height = (rows - 1) * stepY + hexH + 32; hexGridEl.innerHTML = ''; hexGridEl.style.width = `${width}px`; hexGridEl.style.height = `${height}px`; // Center within container const offsetX = Math.floor((hexGridEl.clientWidth - width) / 2); const offsetY = 12; const orderedTraits = [...TRAITS]; // maintain order orderedTraits.forEach((trait, i) => { const col = i % cols; const row = Math.floor(i / cols); const left = offsetX + col * stepX + 16; const top = offsetY + row * stepY + (col % 2 === 1 ? Math.floor(stepY / 2) : 0); const score = state.scores[trait.id] ?? 0; const { hue, sat, light, str } = scoreToHSL(score); const label = scoreLabel(score); const hex = document.createElement('button'); hex.className = 'hex'; hex.style.left = `${left}px`; hex.style.top = `${top}px`; hex.style.width = `${hexW}px`; hex.style.height = `${hexH}px`; hex.setAttribute('aria-label', `${trait.name}, score ${score}, ${label}`); hex.setAttribute('data-trait', trait.id); // Visual layers const ring = document.createElement('div'); ring.className = 'hex-ring'; ring.style.background = `linear-gradient(180deg, ${str}, rgba(0,0,0,0))`; const inner = document.createElement('div'); inner.className = 'hex-inner'; inner.style.background = `linear-gradient(180deg, ${str}, rgba(255,255,255,0.15))`; inner.style.border = '1px solid rgba(255,255,255,0.18)'; inner.style.boxShadow = `inset 0 0 0 1px rgba(255,255,255,0.08), 0 12px 30px -16px ${str}`; const labelEl = document.createElement('div'); labelEl.className = 'hex-label'; labelEl.textContent = trait.name; const scoreEl = document.createElement('div'); scoreEl.className = 'hex-score'; scoreEl.textContent = `${score}`; // Reveal score only if deep unlocked if (state.deepUnlocked) { hex.classList.add('revealed'); } inner.appendChild(labelEl); inner.appendChild(scoreEl); hex.appendChild(ring); hex.appendChild(inner); // Click handler to open panel hex.addEventListener('click', () => openPanel(trait.id)); hexGridEl.appendChild(hex); }); } function openPanel(traitId) { const trait = TRAITS.find(t => t.id === traitId); const score = state.scores[traitId] ?? 0; const { str } = scoreToHSL(score); const label = scoreLabel(score); panelTitle.textContent = trait.name; panelSubtitle.textContent = `${label} • ${score}/23`; panelDesc.textContent = trait.description; panelScore.textContent = `${score} / 23`; panelScoreBar.style.width = `${Math.round((score / 23) * 100)}%`; panelScoreBar.style.background = str; panelBadge.style.background = str; // Growth paths and prompts panelGrowth.innerHTML = ''; panelPrompts.innerHTML = ''; if (state.deepUnlocked) { trait.growthPaths.forEach(g => { const li = document.createElement('li'); li.textContent = g; panelGrowth.appendChild(li); }); trait.prompts.forEach(p => { const li = document.createElement('li'); li.textContent = p; panelPrompts.appendChild(li); }); panelUnlock.classList.add('hidden'); } else { // Lock deeper content; show reflection prompt panelGrowth.innerHTML = `
  • Unlock deeper guidance through sincere self-assessment.
  • `; panelPrompts.innerHTML = `
  • What would someone who knows you well say is your biggest opportunity right now?
  • `; panelUnlock.classList.remove('hidden'); panelUnlockText.innerHTML = ` Depth unlocks when your ratings reflect a mix of genuine strengths and growth edges. Consider reassessing with nuanced honesty. `; } // Open panel panelBackdrop.classList.remove('opacity-0'); panelBackdrop.classList.add('opacity-100'); panelContent.classList.remove('translate-x-full'); } function closePanel() { panelBackdrop.classList.add('opacity-0'); panelBackdrop.classList.remove('opacity-100'); panelContent.classList.add('translate-x-full'); } // ------------------------------ // Navigation and Actions // ------------------------------ function startTest() { state.step = 0; state.answers = new Array(QUESTIONS.length).fill(null); showSection('test'); renderTest(); } function nextStep() { if (state.step < QUESTIONS.length - 1) { state.step++; renderTest(); } else { // Finish -> compute and show map computeScores(); computeSelfBias(); showSection('map'); renderMap(); } } function prevStep() { if (state.step > 0) { state.step--; renderTest(); } } function backToHero() { showSection('hero'); } function retakeAssessment() { state.step = 0; state.answers = new Array(QUESTIONS.length).fill(null); showSection('test'); renderTest(); } function resetAll() { state.step = 0; state.answers = new Array(QUESTIONS.length).fill(null); state.scores = {}; state.selfBiasScore = 0; state.deepUnlocked = false; showSection('hero'); } function loadDemo() { // A plausible distribution: mid-high on some, low on others const demoAnswers = [ 3, 2, // curiosity 2, 3, // discipline 1, 2, // integrity 3, 2, // boldness 3, 3, // empathy 2, 1, // resilience 2, 2, // focus 3, 2, // creativity ].slice(0, QUESTIONS.length); state.answers = demoAnswers.concat(new Array(Math.max(0, QUESTIONS.length - demoAnswers.length)).fill(1)); computeScores(); computeSelfBias(); showSection('map'); renderMap(); } function exportSnapshot() { const payload = { timestamp: new Date().toISOString(), selfBias: Number(state.selfBiasScore.toFixed(2)), deepUnlocked: state.deepUnlocked, traits: TRAITS.map(t => ({ id: t.id, name: t.name, score: state.scores[t.id] ?? 0 })) }; const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `depthmap_snapshot_${Date.now()}.json`; document.body.appendChild(a); a.click(); URL.revokeObjectURL(url); a.remove(); } // ------------------------------ // Event Wiring // ------------------------------ startBtn.addEventListener('click', startTest); loadDemoBtn.addEventListener('click', loadDemo); backToHeroBtn.addEventListener('click', backToHero); prevBtn.addEventListener('click', prevStep); nextBtn.addEventListener('click', nextStep); resetBtn.addEventListener('click', resetAll); retakeBtn.addEventListener('click', retakeAssessment); exportBtn.addEventListener('click', exportSnapshot); panelClose.addEventListener('click', closePanel); panelBackdrop.addEventListener('click', closePanel); // Keyboard: ESC to close document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closePanel(); }); // Handle resize for grid layout window.addEventListener('resize', () => { if (!mapEl.classList.contains('hidden')) { renderMap(); } }); // Initial section showSection('hero');