ia23s
⚙️ UI BUILD PROMPT — Hexagonal Dynamic Trait Mapper
48fe3d7 verified
Raw
History Blame Contribute Delete
21.3 kB
// 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 = `
<div>
<div class="text-sm text-slate-400">Question ${state.step + 1} of ${QUESTIONS.length}</div>
<h3 class="text-xl font-semibold mt-1">${q.text}</h3>
</div>
<div class="px-2 py-1 text-xs rounded-md bg-white/5 border border-white/10 text-slate-300">
${TRAITS.find(t => t.id === q.traitId)?.name ?? ''}
</div>
`;
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 = `
<input id="${id}" type="radio" name="q-${state.step}" value="${opt.value}" ${checked ? 'checked' : ''} class="sr-only" />
<div class="w-5 h-5 rounded-full border ${checked ? 'border-gold-500' : 'border-white/30'} flex items-center justify-center">
<div class="w-2.5 h-2.5 rounded-full ${checked ? 'bg-gold-500' : 'bg-transparent'}"></div>
</div>
<div class="text-sm">${opt.label}</div>
`;
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 = `
<li class="text-slate-300/80">Unlock deeper guidance through sincere self-assessment.</li>
`;
panelPrompts.innerHTML = `
<li class="text-slate-300/80">What would someone who knows you well say is your biggest opportunity right now?</li>
`;
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');