Research Date: February 11, 2026
Current State: Production-ready v1.0 with PWA, analytics, testing, accessibility
- Basic XP/leveling (1000 XP per level)
- Badge system for milestones
- Daily streak tracking
- Milestone celebrations with confetti
Implementation:
// Variable Ratio Schedule - Surprise bonuses
function calculateXPReward(skillId: string): number {
const baseXP = skillData[skillId].xpReward;
const bonusChance = Math.random();
if (bonusChance < 0.1) return baseXP * 2; // 10% chance 2x XP
if (bonusChance < 0.25) return baseXP * 1.5; // 15% chance 1.5x XP
return baseXP;
}
// Loot Box System for badges
interface LootBox {
rarity: 'common' | 'rare' | 'epic' | 'legendary';
contents: Array<Badge | XPBoost | CustomAvatar>;
}Psychological Impact: Dopamine spikes from unpredictability increase engagement 40-60%.
Features:
- Leaderboards with filters (friends, global, category-specific)
- Achievement Rarity - Show % of users who unlocked each badge
- Progress Sharing - Social media integration with beautiful cards
- Challenges - Weekly/monthly community goals
Psychology: Social proof and FOMO (Fear of Missing Out) drive 35% higher retention.
Implementation:
// Skill decay with notification
if (daysSinceLastPractice > 30) {
status = 'decaying';
notification: "Your JavaScript skills are getting rusty! 📉";
motivation: "Complete a refresh challenge to prevent skill decay";
}
// Streak protection tokens
interface StreakShield {
count: number; // Allow 1-3 missed days
earnedBy: 'achievement' | 'purchase';
}Research: Loss aversion is 2.5x stronger than gain motivation (Kahneman & Tversky).
Concepts:
- Skill Tree Animations - Growing tree metaphor (branches bloom as you learn)
- Heat Maps - Activity intensity visualization
- Progress Rings - macOS Activity Rings style for daily goals
- Before/After Comparisons - Show skill tree from start vs. now
Features:
interface LearningJourney {
currentChapter: number;
narrative: string; // "You've entered the Frontend Kingdom..."
characterLevel: number;
unlockedPaths: string[];
}
// Tiered narrative arcs
const narratives = {
1-10: "The Apprentice's Journey",
11-25: "Rising Developer",
26-50: "Master Craftsperson",
51+: "Legendary Architect"
};Impact: Story-driven gamification increases completion rates by 30%.
- Static skill tree with fixed prerequisites
- No personalization or recommendations
Algorithm: Collaborative Filtering + Content-Based Recommendations
# Simplified recommendation engine
class SkillRecommender:
def __init__(self):
self.user_profiles = {} # Learning style, pace, interests
self.skill_embeddings = {} # Vector representations
def recommend_next_skills(self, user_id, n=5):
# User's completed skills
completed = self.user_profiles[user_id]['completed']
# Similar users' paths
similar_users = self.find_similar_learners(user_id)
# Skills they completed that user hasn't
candidates = self.get_candidate_skills(completed, similar_users)
# Rank by: difficulty match, interest alignment, career goal relevance
ranked = self.rank_skills(user_id, candidates)
return ranked[:n]Data Collection:
- Time to complete each skill
- Failed/passed quiz attempts
- Resources clicked vs. completed
- Skills refreshed frequency
Implementation:
interface AdaptiveDifficulty {
userProficiency: number; // 0-100
skillDifficulty: number; // 0-100
recommendedChallenge: 'easy' | 'optimal' | 'hard';
}
// Zone of Proximal Development (Vygotsky)
function getOptimalChallenge(proficiency: number): number {
return proficiency + 15; // Slightly above current level
}
// Dynamic quiz difficulty
function selectQuizQuestions(skillId: string, userLevel: number): Question[] {
const questions = questionBank[skillId];
// IRT (Item Response Theory) - adaptive testing
return questions
.filter(q => Math.abs(q.difficulty - userLevel) < 20)
.sort(() => 0.5 - Math.random())
.slice(0, 5);
}Modalities:
interface LearningStyle {
visual: number; // Videos, diagrams, infographics
auditory: number; // Podcasts, audio explanations
kinesthetic: number; // Interactive exercises, coding challenges
reading: number; // Articles, documentation, books
}
// Implicit detection from resource preferences
function detectLearningStyle(userId: string): LearningStyle {
const history = getResourceClickHistory(userId);
return {
visual: countResourceType(history, 'video') / history.length,
auditory: countResourceType(history, 'podcast') / history.length,
kinesthetic: countResourceType(history, 'interactive') / history.length,
reading: countResourceType(history, 'article') / history.length
};
}
// Personalized resource ordering
function sortResources(resources: Resource[], style: LearningStyle): Resource[] {
return resources.sort((a, b) => {
const scoreA = style[a.type];
const scoreB = style[b.type];
return scoreB - scoreA;
});
}Algorithm: SM-2 (SuperMemo 2) algorithm
interface SpacedRepetition {
skillId: string;
easeFactor: number; // 1.3 - 2.5 (how easy to recall)
interval: number; // Days until next review
repetitions: number;
nextReviewDate: Date;
}
function calculateNextReview(
card: SpacedRepetition,
quality: number // 0-5 (0=total blackout, 5=perfect)
): SpacedRepetition {
let { easeFactor, interval, repetitions } = card;
if (quality < 3) {
// Failed - restart
repetitions = 0;
interval = 1;
} else {
// Passed
if (repetitions === 0) {
interval = 1;
} else if (repetitions === 1) {
interval = 6;
} else {
interval = Math.round(interval * easeFactor);
}
repetitions++;
easeFactor = Math.max(1.3,
easeFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
);
}
return {
...card,
easeFactor,
interval,
repetitions,
nextReviewDate: addDays(new Date(), interval)
};
}Integration: Trigger review reminders via push notifications or email.
ML Model: Decision Tree / Random Forest
interface CareerGoal {
role: 'frontend' | 'backend' | 'fullstack' | 'ai-engineer' | 'devops';
requiredSkills: string[];
recommendedSkills: string[];
estimatedTimeToGoal: number; // weeks
}
// Predict user's career trajectory
async function predictCareerPath(userId: string): Promise<CareerGoal> {
const profile = await getUserLearningProfile(userId);
// ML model trained on successful learner paths
const prediction = await mlModel.predict({
completedSkills: profile.completed,
timePerSkill: profile.avgTimePerSkill,
preferredCategories: profile.topCategories,
currentLevel: profile.level
});
return prediction.mostLikelyCareer;
}- Single-player experience only
- No social features or collaboration
Features:
interface StudyGroup {
id: string;
name: string;
members: User[];
focusSkills: string[];
meetingSchedule: 'daily' | 'weekly' | 'biweekly';
groupGoals: GroupGoal[];
}
interface GroupGoal {
title: string;
targetSkills: string[];
deadline: Date;
progress: number; // 0-100%
rewards: GroupBadge[];
}
// Group XP multiplier
function calculateGroupXP(baseXP: number, groupSize: number): number {
const multiplier = 1 + (groupSize * 0.1); // +10% per member
return Math.round(baseXP * Math.min(multiplier, 2.0)); // Max 2x
}Research: Peer accountability increases completion rates by 65% (Stickk.com study).
Features:
interface Mentorship {
mentor: User;
mentee: User;
focusAreas: string[];
sessions: MentorSession[];
goals: MentorshipGoal[];
}
// Mentor matching algorithm
function findMentors(userId: string, skillId: string): User[] {
return users
.filter(u => u.completed.includes(skillId))
.filter(u => u.mentorAvailable)
.filter(u => u.timezone_offset - userTimezone < 4) // Similar timezones
.sort((a, b) => b.mentorRating - a.mentorRating)
.slice(0, 10);
}
// Mentor rewards
const mentorBenefits = {
xpBonus: 50, // per session
specialBadges: ['Master Teacher', 'Community Leader'],
unlockAdvancedSkills: true
};Psychology: Teaching reinforces learning (Protégé Effect) - mentors gain 90% knowledge retention.
Implementation:
interface ProjectSubmission {
userId: string;
skillId: string;
repositoryUrl: string;
description: string;
requestedFeedback: string[];
reviews: PeerReview[];
}
interface PeerReview {
reviewerId: string;
rating: number; // 1-5
comments: ReviewComment[];
suggestions: string[];
approved: boolean;
}
// Incentivize reviewing
const reviewRewards = {
xpPerReview: 25,
badgeAfterReviews: { count: 10, badge: 'Helpful Reviewer' }
};Tools:
- Live coding sessions - VS Code Live Share integration
- Screen sharing - For pair programming
- Chat & voice - Discord/Slack-style communication
- Shared whiteboards - Excalidraw/Miro integration
Tech Stack:
// WebRTC for peer-to-peer connections
import { Peer } from 'peerjs';
// Socket.io for real-time events
import { Server } from 'socket.io';
// Y.js for CRDT - Conflict-free Replicated Data Types
import * as Y from 'yjs';Features:
interface CommunityChallenge {
id: string;
title: string;
description: string;
category: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
startDate: Date;
endDate: Date;
participants: string[];
submissions: Submission[];
prizes: Prize[];
}
// Gamification
const challengeRewards = {
participation: 100, // XP
top10: { xp: 500, badge: 'Challenge Champion' },
winner: { xp: 1000, badge: 'Grand Master', customAvatar: true }
};- Basic analytics (learning velocity, category breakdown)
- Simple activity timeline
- Streak tracking
Cognitive Levels:
enum BloomLevel {
Remember = 1, // Recall facts
Understand = 2, // Explain ideas
Apply = 3, // Use in new situations
Analyze = 4, // Draw connections
Evaluate = 5, // Justify decisions
Create = 6 // Produce new work
}
interface SkillMastery {
skillId: string;
bloomLevels: Map<BloomLevel, number>; // 0-100 proficiency
overallMastery: number;
}
// Track progression through Bloom's levels
function assessBloomLevel(quizResults: QuizResult[]): BloomLevel {
// Question types mapped to Bloom levels
const questionAnalysis = analyzeQuestionTypes(quizResults);
if (questionAnalysis.createQuestions > 0.7) return BloomLevel.Create;
if (questionAnalysis.analyzeQuestions > 0.6) return BloomLevel.Analyze;
if (questionAnalysis.applyQuestions > 0.5) return BloomLevel.Apply;
// ... etc
}Metrics:
interface LearningCurve {
skillId: string;
dataPoints: Array<{
timestamp: Date;
proficiency: number; // 0-100
effort: number; // hours spent
}>;
curve: 'steep' | 'gradual' | 'plateau';
projectedMastery: Date;
}
// Fit exponential learning curve
function fitLearningCurve(data: DataPoint[]): CurveParameters {
// P(t) = A(1 - e^(-kt))
// P = proficiency, t = time, A = asymptotic performance, k = learning rate
return nonlinearLeastSquares(data, exponentialModel);
}
// Predict time to mastery
function predictMasteryDate(curve: CurveParameters, target: number = 90): Date {
const hoursNeeded = -Math.log(1 - target/curve.A) / curve.k;
return addHours(new Date(), hoursNeeded);
}Implementation:
interface ConceptNode {
id: string;
label: string;
mastery: number;
relatedConcepts: string[];
prerequisites: string[];
}
interface KnowledgeGraph {
nodes: ConceptNode[];
edges: Array<{
from: string;
to: string;
relationship: 'prerequisite' | 'related' | 'application';
strength: number;
}>;
}
// Identify knowledge gaps
function findKnowledgeGaps(userId: string): ConceptNode[] {
const graph = getUserKnowledgeGraph(userId);
return graph.nodes.filter(node => {
// Node with low mastery but many dependent nodes
const dependents = graph.edges
.filter(e => e.from === node.id)
.map(e => graph.nodes.find(n => n.id === e.to));
return node.mastery < 60 && dependents.length > 2;
});
}Features:
interface ReflectionPrompt {
question: string;
frequency: 'after_skill' | 'weekly' | 'monthly';
category: 'difficulty' | 'enjoyment' | 'application' | 'confidence';
}
const reflectionPrompts = [
"Rate your confidence in applying this skill (1-10)",
"What was the most challenging aspect?",
"Where do you see yourself using this skill?",
"What would you like to learn next?"
];
// Self-assessment accuracy
interface SelfAssessment {
userRating: number;
actualPerformance: number;
accuracy: number; // How well user judges their ability
}
// Improve metacognition over time
function trackMetacognition(userId: string): MetacognitionScore {
const assessments = getUserSelfAssessments(userId);
const accuracy = assessments.map(a =>
1 - Math.abs(a.userRating - a.actualPerformance) / 10
).reduce((a, b) => a + b) / assessments.length;
return {
accuracy, // 0-1
trend: calculateTrend(assessments),
feedback: generateMetacognitionFeedback(accuracy)
};
}KPIs:
interface EfficiencyMetrics {
timeToMastery: number; // hours
resourceUtilization: number; // % of resources accessed
quizAttemptRatio: number; // passes / total attempts
retentionRate: number; // % of skills retained after 30 days
transferAbility: number; // applying skills in new contexts
}
// Optimize learning efficiency
function getEfficiencyInsights(metrics: EfficiencyMetrics): Insight[] {
const insights: Insight[] = [];
if (metrics.resourceUtilization < 0.3) {
insights.push({
type: 'warning',
message: "You're relying heavily on trial and error. Try exploring more resources!",
action: 'explore_resources'
});
}
if (metrics.quizAttemptRatio < 0.5) {
insights.push({
type: 'tip',
message: "Consider reviewing materials before attempting quizzes",
action: 'review_resources'
});
}
return insights;
}- Static skill tree data in TypeScript files
- Manual content creation
- No admin interface
Options:
1. Sanity.io (Recommended)
// Schema definition
export default {
name: 'skill',
title: 'Skill',
type: 'document',
fields: [
{ name: 'title', type: 'string' },
{ name: 'description', type: 'text' },
{ name: 'tier', type: 'string' },
{ name: 'prerequisites', type: 'array', of: [{ type: 'reference', to: [{ type: 'skill' }] }] },
{ name: 'resources', type: 'array', of: [{ type: 'resource' }] },
{ name: 'quiz', type: 'array', of: [{ type: 'question' }] }
]
};
// Groq query for fetching
const query = `*[_type == "skill"]{
_id,
title,
description,
prerequisites[]->,
resources,
quiz
}`;2. Contentful
- Rich text editor
- Media management
- Localization support
- GraphQL API
3. Strapi (Self-hosted)
- Full control
- Custom plugins
- Role-based access
Use Cases:
// Generate skill descriptions
async function generateSkillDescription(skillName: string): Promise<string> {
const prompt = `
Create a concise, engaging description for a web development skill: "${skillName}".
Include:
- What it is (1 sentence)
- Why it's useful (1 sentence)
- What you'll learn (3-4 bullet points)
Keep it motivational and clear.
`;
return await openai.complete(prompt);
}
// Generate quiz questions
async function generateQuiz(skillId: string, difficulty: string): Promise<Question[]> {
const skillContent = await getSkillContent(skillId);
const prompt = `
Generate 5 ${difficulty} multiple-choice questions about: ${skillContent.title}
Context: ${skillContent.description}
Key concepts: ${skillContent.keyConcepts.join(', ')}
Format: JSON array of questions with:
- question (string)
- options (array of 4 strings)
- correctAnswer (number, 0-3)
- explanation (string)
`;
return await openai.complete(prompt, { format: 'json' });
}
// Curate learning resources
async function findResources(skillName: string): Promise<Resource[]> {
// 1. Search YouTube API
const videos = await youtube.search({
q: `${skillName} tutorial`,
type: 'video',
videoDuration: 'medium', // 4-20 minutes
relevanceLanguage: 'en'
});
// 2. Search dev.to articles
const articles = await devTo.search({
tag: skillName.toLowerCase(),
sort: 'relevance'
});
// 3. Find official documentation
const docs = await findOfficialDocs(skillName);
// 4. AI-filter for quality
const filtered = await filterWithAI([...videos, ...articles, ...docs]);
return filtered.slice(0, 5);
}Features:
interface CommunitySkill {
id: string;
createdBy: string;
status: 'draft' | 'review' | 'approved' | 'rejected';
title: string;
description: string;
reviewsNeeded: number;
upvotes: number;
qualityScore: number;
}
// Peer review system
async function submitSkillForReview(skill: CommunitySkill): Promise<void> {
skill.status = 'review';
skill.reviewsNeeded = 3;
// Notify experienced users
const reviewers = await findQualifiedReviewers(skill.category);
await notifyReviewers(reviewers, skill);
}
// Approve based on consensus
function checkApprovalStatus(skill: CommunitySkill): boolean {
const reviews = getSkillReviews(skill.id);
const approvals = reviews.filter(r => r.approved).length;
const rejections = reviews.filter(r => !r.approved).length;
if (approvals >= 3) {
skill.status = 'approved';
rewardContributor(skill.createdBy, { xp: 500, badge: 'Content Creator' });
return true;
}
if (rejections >= 2) {
skill.status = 'rejected';
notifyContributor(skill.createdBy, reviews);
return false;
}
return false; // Still pending
}System:
interface SkillVersion {
skillId: string;
version: string; // semver: 1.0.0
changes: string[];
migrateUserData: (oldData: any) => any;
deprecated: boolean;
}
// Track versions
const versionHistory: Map<string, SkillVersion[]> = new Map();
// Notify users of updates
async function handleSkillUpdate(skillId: string, newVersion: SkillVersion): Promise<void> {
const usersAffected = await findUsersWithSkill(skillId);
for (const user of usersAffected) {
await notifyUpdate(user, {
skillId,
message: `${skillId} has been updated! Check out what's new.`,
changes: newVersion.changes,
action: 'review_skill'
});
// Offer re-certification
if (newVersion.changes.includes('major')) {
await offerRecertification(user, skillId);
}
}
}- PWA with basic offline support
- Responsive design
- Service worker caching
Architecture:
// Shared codebase with web
// skill-mapper/
// ├── packages/
// │ ├── web/ # Next.js app
// │ ├── mobile/ # React Native app
// │ └── shared/ # Shared logic, types, stores
// │ ├── stores/
// │ ├── utils/
// │ └── types/
// Use react-native-web for maximum code reuse
import { View, Text, TouchableOpacity } from 'react-native';
// Platform-specific code
import { Platform } from 'react-native';
const styles = Platform.select({
ios: { borderRadius: 10 },
android: { borderRadius: 5 },
web: { borderRadius: 8 }
});Benefits:
- Push notifications (re-engagement)
- Offline-first with local SQLite
- Better performance (60fps animations)
- App Store presence (discovery)
Interactions:
// Gestures
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
// Swipe to navigate skills
const swipeGesture = Gesture.Pan()
.onUpdate((e) => {
// Horizontal swipe = navigate between skills
if (Math.abs(e.translationX) > 100) {
navigateToNextSkill(e.translationX > 0 ? 'prev' : 'next');
}
});
// Long press for quick actions
const longPressGesture = Gesture.LongPress()
.minDuration(500)
.onStart(() => {
showQuickActions(); // Mark complete, add to favorites, etc.
});
// Pinch to zoom skill tree
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
setZoomLevel(e.scale);
});Mobile-Specific Features:
- Voice Input - "Alexa, what should I learn next?"
- Camera Integration - Scan QR codes to join study groups
- Biometric Auth - Face ID / Touch ID
- Haptic Feedback - Vibration on level up
Techniques:
// 1. List virtualization
import { FlashList } from '@shopify/flash-list';
<FlashList
data={skills}
renderItem={({ item }) => <SkillCard skill={item} />}
estimatedItemSize={100}
/>
// 2. Image optimization
import FastImage from 'react-native-fast-image';
<FastImage
source={{ uri: badge.imageUrl, priority: FastImage.priority.high }}
resizeMode={FastImage.resizeMode.contain}
/>
// 3. Code splitting
import { lazy } from 'react';
const AnalyticsDashboard = lazy(() => import('./AnalyticsDashboard'));
// 4. Hermes JavaScript engine (Android)
// 50% faster startup time
// 5. Reanimated for 60fps animations
import Animated, { useAnimatedStyle } from 'react-native-reanimated';Strategy:
// Local-first database
import WatermelonDB from '@nozbe/watermelondb';
const database = new Database({
adapter: new SQLiteAdapter({ schema }),
modelClasses: [Skill, User, Progress]
});
// Sync protocol
async function syncWithServer(): Promise<void> {
const lastSyncTime = await getLastSyncTime();
// 1. Pull changes from server
const serverChanges = await api.getChangesSince(lastSyncTime);
await applyChangesLocally(serverChanges);
// 2. Push local changes to server
const localChanges = await getLocalChangesSince(lastSyncTime);
await api.pushChanges(localChanges);
// 3. Resolve conflicts
const conflicts = await detectConflicts();
await resolveConflicts(conflicts); // Last-write-wins or custom logic
await setLastSyncTime(Date.now());
}- Free, open-source project
- No monetization
Free Tier:
- Core skill tree (50 skills)
- Basic gamification
- Limited community features
Premium Tier ($9.99/month or $99/year):
- Full skill tree (500+ skills)
- Advanced analytics
- AI-powered recommendations
- Priority support
- Exclusive badges & avatars
- Certificate generation
- Offline mobile app
enum SubscriptionTier {
Free = 'free',
Pro = 'pro',
Team = 'team', // $49/month for 10 users
Enterprise = 'enterprise' // Custom pricing
}
interface UserSubscription {
tier: SubscriptionTier;
startDate: Date;
endDate: Date;
stripeCustomerId: string;
features: FeatureFlag[];
}
// Feature gating
function canAccessFeature(user: User, feature: string): boolean {
const tierFeatures = {
free: ['basic_tree', 'simple_analytics'],
pro: ['full_tree', 'advanced_analytics', 'ai_recommendations', 'certificates'],
team: ['all_pro_features', 'team_dashboard', 'group_goals'],
enterprise: ['all_features', 'sso', 'custom_branding', 'api_access']
};
return tierFeatures[user.subscription.tier].includes(feature);
}Model: Take 20-30% commission on premium courses
interface PremiumCourse {
id: string;
creator: string; // User or organization
title: string;
skills: string[]; // Associated skills
price: number;
students: number;
rating: number;
content: CourseModule[];
}
// Revenue share
const revenueShare = {
platform: 0.25, // 25%
creator: 0.70, // 70%
referrer: 0.05 // 5% for affiliate referrals
};Features:
- Custom Skill Trees - Company-specific technologies
- Team Management - Onboarding, progress tracking
- SSO Integration - Okta, Azure AD
- Reporting - Team analytics, skill gaps
- API Access - Integration with internal tools
- White-Label - Custom branding
Pricing: $10-50 per user/month
Opportunities:
- Course Platforms - Udemy, Coursera affiliate links (10-20% commission)
- Tool Vendors - Sponsor skills (e.g., "MongoDB Professional" badge)
- Bootcamps - Partner for student acquisition
- Job Boards - Integration with Indeed, LinkedIn (placement fees)
Implementation:
interface Certification {
id: string;
title: string; // "Full-Stack Developer Certification"
requiredSkills: string[];
optionalSkills: string[];
proctoreExam: boolean;
fee: number; // $49-199
validity: number; // months until renewal
verifiable: boolean; // Blockchain-verified
}
// Revenue model
const certificationRevenue = {
exam: 49, // One-time fee
renewal: 29, // Annual renewal
rush: 99, // Fast-track (7 days vs 30 days)
};- Standalone application
- No external integrations
Notion Integration:
// Create database in Notion for each skill
async function createNotionPage(skillId: string): Promise<string> {
const skill = getSkill(skillId);
const response = await notion.pages.create({
parent: { database_id: userNotionDatabaseId },
properties: {
Title: { title: [{ text: { content: skill.title } }] },
Status: { select: { name: 'In Progress' } },
Category: { select: { name: skill.category } },
Resources: { url: skill.resources[0]?.url }
},
children: [
{
object: 'block',
type: 'heading_2',
heading_2: { rich_text: [{ text: { content: 'Learning Notes' } }] }
},
{
object: 'block',
type: 'paragraph',
paragraph: { rich_text: [{ text: { content: skill.description } }] }
}
]
});
return response.url;
}Obsidian Integration:
// Generate Markdown files
function generateObsidianNote(skillId: string): string {
const skill = getSkill(skillId);
return `---
title: ${skill.title}
category: ${skill.category}
status: ${skill.status}
prerequisites: [${skill.prerequisites.join(', ')}]
---
# ${skill.title}
## Description
${skill.description}
## Resources
${skill.resources.map(r => `- [${r.label}](${r.url})`).join('\n')}
## Notes
<!-- Add your learning notes here -->
## Related
${skill.prerequisites.map(p => `[[${p}]]`).join(', ')}
`;
}Integration:
// Generate Anki flashcards
async function exportToAnki(skillId: string): Promise<void> {
const skill = getSkill(skillId);
const questions = skill.quiz;
const deck = {
name: `Skill Mapper - ${skill.title}`,
cards: questions.map(q => ({
front: q.question,
back: `${q.options[q.correctAnswer]}\n\nExplanation: ${q.explanation}`,
tags: [skill.category, skill.tier]
}))
};
// Export as .apkg file
await ankiConnect.createDeck(deck);
}Features:
// Google Calendar
async function scheduleStudyTime(skillId: string, duration: number): Promise<void> {
const skill = getSkill(skillId);
const event = {
summary: `Learn: ${skill.title}`,
description: `${skill.description}\n\nResources:\n${skill.resources.map(r => r.url).join('\n')}`,
start: { dateTime: new Date().toISOString() },
end: { dateTime: addMinutes(new Date(), duration).toISOString() },
reminders: {
useDefault: false,
overrides: [
{ method: 'popup', minutes: 10 }
]
}
};
await calendar.events.insert({ calendarId: 'primary', resource: event });
}
// Apple Calendar
// Similar implementation with EventKit frameworkFeatures:
// Link skills to repositories
interface GitHubProject {
skillsApplied: string[];
repositoryUrl: string;
description: string;
stars: number;
verifiedBy: 'commits' | 'pr' | 'issue';
}
// Verify skill through GitHub activity
async function verifySkillViaGitHub(
userId: string,
skillId: string
): Promise<boolean> {
const repos = await github.getUserRepos(userId);
const skill = getSkill(skillId);
// Check for relevant tech in repositories
for (const repo of repos) {
const languages = await github.getRepoLanguages(repo);
const topics = repo.topics;
if (topics.includes(skill.title.toLowerCase()) ||
languages.includes(skill.category)) {
return true;
}
}
return false;
}Integrations:
- Moodle - Import/export via SCORM packages
- Canvas - LTI (Learning Tools Interoperability)
- Blackboard - REST API integration
- Google Classroom - Assignment integration
// LTI integration
interface LTIParams {
lti_message_type: 'basic-lti-launch-request';
lti_version: 'LTI-1p0';
resource_link_id: string;
user_id: string;
roles: string;
}
// Single Sign-On via LTI
function handleLTILaunch(params: LTIParams): void {
// Validate OAuth signature
if (validateLTISignature(params)) {
// Create or update user
const user = upsertUser({
ltiUserId: params.user_id,
role: params.roles
});
// Redirect to skill tree
redirectToSkillTree(user);
}
}- WCAG 2.1 AA compliant
- ARIA labels and live regions
- Keyboard navigation
- Screen reader support
Features:
// Reading level adjustment
interface ContentReadability {
fleschScore: number; // 0-100 (higher = easier)
gradeLevel: number; // US grade level
simplifiedVersion: string;
}
async function simplifyContent(text: string, targetLevel: number): Promise<string> {
// Use AI to rewrite at target reading level
return await openai.complete(`
Rewrite this at a ${targetLevel}th grade reading level:
${text}
`);
}
// Dyslexia-friendly font
const dyslexiaFont = {
fontFamily: 'OpenDyslexic, sans-serif',
letterSpacing: '0.1em',
wordSpacing: '0.2em',
lineHeight: 1.8
};
// ADHD-friendly mode
const adhdfriendlyMode = {
reduceAnimations: true,
focusOnOneTask: true,
breakLongContent: true,
timeEstimates: true, // "This will take ~15 minutes"
progressIndicators: true
};Technologies:
- Screen Magnifiers - High contrast, large text mode
- Voice Control - Dragon NaturallySpeaking, Voicecream
- Eye Tracking - Tobii, EyeGaze
- Switch Access - Single button navigation
// Switch access navigation
interface SwitchAccessConfig {
scanSpeed: number; // ms between highlights
scanMode: 'auto' | 'manual';
highlightColor: string;
selectAction: 'dwell' | 'switch';
}
// Implement scanning
let currentIndex = 0;
function startScanning(elements: HTMLElement[], config: SwitchAccessConfig): void {
setInterval(() => {
elements[currentIndex].classList.remove('highlighted');
currentIndex = (currentIndex + 1) % elements.length;
elements[currentIndex].classList.add('highlighted');
}, config.scanSpeed);
}
// Select on switch press
document.addEventListener('keydown', (e) => {
if (e.key === config.selectKey) {
elements[currentIndex].click();
}
});Autism Spectrum:
const autismFriendlyMode = {
// Reduce sensory overload
disableAnimations: true,
simplifyUI: true,
muteColors: true,
// Increase predictability
showStepsExplicitly: true,
avoidMetaphors: true,
provideRoutines: true,
// Support processing time
removelTimeConstraints: true,
allowPauses: true,
extendedQuizTime: true
};Learning Disabilities:
// Multi-sensory learning
interface MultiSensoryContent {
visual: string; // Diagrams, infographics
auditory: string; // Audio narration
kinesthetic: string; // Interactive exercises
readable: string; // Text content
}
// Present same content in multiple formats
function presentContent(content: MultiSensoryContent, preferences: string[]): void {
preferences.forEach(pref => {
renderContent(content[pref]);
});
}Implementation:
// next-i18next
import { useTranslation } from 'next-i18next';
function SkillCard({ skill }: Props) {
const { t } = useTranslation('skills');
return (
<div>
<h3>{t(skill.titleKey)}</h3>
<p>{t(skill.descriptionKey)}</p>
</div>
);
}
// Right-to-left (RTL) support
const isRTL = ['ar', 'he', 'fa', 'ur'].includes(locale);
<div dir={isRTL ? 'rtl' : 'ltr'}>
{/* Content */}
</div>
// Locale-specific formatting
const formattedDate = new Intl.DateTimeFormat(locale).format(date);
const formattedNumber = new Intl.NumberFormat(locale).format(xp);Options:
interface AccessibilityPreferences {
// Visual
highContrast: boolean;
darkMode: boolean;
fontSize: 'small' | 'medium' | 'large' | 'xlarge';
colorBlindMode: 'none' | 'deuteranopia' | 'protanopia' | 'tritanopia';
// Audio
soundEnabled: boolean;
screenReaderOptimized: boolean;
audioDescriptions: boolean;
// Interaction
reducedMotion: boolean;
keyboardOnly: boolean;
switchAccess: boolean;
mouseDwellTime: number; // ms
// Cognitive
simplifiedLanguage: boolean;
readingLevel: number; // grade level
focusMode: boolean; // Hide distractions
breakReminders: boolean;
}
// Persist across sessions
const savePreferences = (prefs: AccessibilityPreferences) => {
localStorage.setItem('a11y-prefs', JSON.stringify(prefs));
};- React Flow with ~50-100 skills
- Basic memoization
- Performance starts degrading at 500+ nodes
Implementation:
// Only render visible nodes
function useViewportCulling(
nodes: Node[],
viewport: { x: number; y: number; zoom: number }
): Node[] {
return useMemo(() => {
const { x, y, zoom } = viewport;
const viewportWidth = window.innerWidth / zoom;
const viewportHeight = window.innerHeight / zoom;
return nodes.filter(node => {
const nodeX = node.position.x;
const nodeY = node.position.y;
// Check if node is in viewport (with buffer)
const buffer = 200; // pixels
return (
nodeX >= x - buffer &&
nodeX <= x + viewportWidth + buffer &&
nodeY >= y - buffer &&
nodeY <= y + viewportHeight + buffer
);
});
}, [nodes, viewport]);
}
// React Flow viewport
const visibleNodes = useViewportCulling(allNodes, reactFlowInstance.getViewport());Strategy:
// Different detail levels based on zoom
enum DetailLevel {
Full = 'full', // Zoom > 1.0 - Full detail
Medium = 'medium', // 0.5 < Zoom <= 1.0 - Some details hidden
Low = 'low', // 0.25 < Zoom <= 0.5 - Minimal detail
Icon = 'icon' // Zoom <= 0.25 - Just icons
}
function determineDetailLevel(zoom: number): DetailLevel {
if (zoom > 1.0) return DetailLevel.Full;
if (zoom > 0.5) return DetailLevel.Medium;
if (zoom > 0.25) return DetailLevel.Low;
return DetailLevel.Icon;
}
// Render based on LOD
function CustomNode({ data, zoom }: Props) {
const lod = determineDetailLevel(zoom);
switch (lod) {
case DetailLevel.Full:
return <FullSkillCard data={data} />;
case DetailLevel.Medium:
return <SimpleSkillCard data={data} />;
case DetailLevel.Low:
return <MinimalSkillCard data={data} />;
case DetailLevel.Icon:
return <SkillIcon data={data} />;
}
}Approach:
// Group nearby skills into clusters
interface Cluster {
id: string;
skills: Skill[];
bounds: { x: number; y: number; width: number; height: number };
collapsed: boolean;
}
// K-means clustering
function clusterSkills(skills: Skill[], k: number): Cluster[] {
// 1. Initialize k random centroids
let centroids = randomSample(skills, k).map(s => s.position);
// 2. Iterate until convergence
for (let i = 0; i < 100; i++) {
// Assign skills to nearest centroid
const assignments = skills.map(skill => {
const distances = centroids.map(c => distance(skill.position, c));
return { skill, cluster: argMin(distances) };
});
// Recalculate centroids
for (let j = 0; j < k; j++) {
const clusterSkills = assignments
.filter(a => a.cluster === j)
.map(a => a.skill);
centroids[j] = {
x: mean(clusterSkills.map(s => s.position.x)),
y: mean(clusterSkills.map(s => s.position.y))
};
}
}
// 3. Create cluster objects
return centroids.map((centroid, i) => ({
id: `cluster-${i}`,
skills: assignments.filter(a => a.cluster === i).map(a => a.skill),
bounds: calculateBounds(assignments.filter(a => a.cluster === i)),
collapsed: true // Start collapsed
}));
}
// Render clusters
function renderClusters(clusters: Cluster[]): Node[] {
return clusters.map(cluster => {
if (cluster.collapsed) {
// Show cluster node
return {
id: cluster.id,
type: 'clusterNode',
position: { x: cluster.bounds.x, y: cluster.bounds.y },
data: { count: cluster.skills.length, category: cluster.skills[0].category }
};
} else {
// Show individual skills
return cluster.skills;
}
}).flat();
}Strategy:
// Load skills in chunks
async function loadSkillsProgressive(
onChunkLoadLoad: (skills: Skill[]) => void
): Promise<void> {
const CHUNK_SIZE = 50;
// 1. Load critical skills first (no prerequisites)
const criticalSkills = await fetchSkills({ prerequisites: [] });
onChunkLoaded(criticalSkills);
// 2. Load tier 1
setTimeout(async () => {
const tier1 = await fetchSkills({ tier: 1 });
onChunkLoaded(tier1);
}, 100);
// 3. Load remaining tiers
for (let tier = 2; tier <= 5; tier++) {
setTimeout(async () => {
const tierSkills = await fetchSkills({ tier });
onChunkLoaded(tierSkills);
}, tier * 200);
}
}
// Show loading skeleton
function SkillTreeSkeleton({ count }: Props) {
return (
<>
{Array(count).fill(0).map((_, i) => (
<SkeletonNode key={i} />
))}
</>
);
}Offload to Background:
// worker.ts
import { exposeexpose } from 'comlink';
const api = {
calculateLayout(nodes: Node[], edges: Edge[]): Layout {
// Heavy Dagre layout calculation
return dagre.layout({ nodes, edges });
},
findShortestPath(start: string, end: string, graph: Graph): string[] {
// Dijkstra's algorithm
return dijkstra(graph, start, end);
},
clusterSkills(skills: Skill[], k: number): Cluster[] {
// K-means clustering
return kmeans(skills, k);
}
};
expose(api);
// main.ts
import { wrap } from 'comlink';
const worker = new Worker(new URL('./worker.ts', import.meta.url));
const api = wrap<typeof import('./worker').api>(worker);
// Use worker for heavy operations
const layout = await api.calculateLayout(nodes, edges);Alternative: Use Canvas for rendering @1000+ nodes
// react-flow with canvas renderer
import { ReactFlow, CanvasRenderer } from 'react-flow-renderer';
<ReactFlow
nodes={nodes}
edges={edges}
renderer={CanvasRenderer} // Much faster for large graphs
/>
// Or custom Canvas implementation
function SkillTreeCanvas({ skills }: Props) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (ctx) {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Render skills
skills.forEach(skill => {
ctx.fillStyle = getSkillColor(skill.status);
ctx.fillRect(skill.x, skill.y, skill.width, skill.height);
ctx.fillText(skill.title, skill.x + 10, skill.y + 20);
});
}
}, [skills]);
return <canvas ref={canvasRef} width={2000} height={2000} />;
}- AI-Powered Recommendations - High impact on engagement (30-50% increase)
- Social Features - Study groups, mentorship (65% completion boost)
- Mobile App (React Native) - Huge market, better UX
- Advanced Analytics - Learning curve, metacognition, Bloom's taxonomy
- Spaced Repetition - scientifically proven retention improvement
- Community Content - Scale content creation
- Notion/Obsidian Integration - Requested feature
- Certificate Program - Monetization opportunity
- Voice/Gesture Controls - Differentiation feature
- Large-Scale Performance - Prepare for growth
- VR/AR Experience - Emerging tech, experimental
- Blockchain Certificates - Niche audience
- Custom Skill Tree Editor - Power user feature
- White-Label Enterprise - Requires sales team
- Weeks 1-2: User profile data collection
- Weeks 3-4: Recommendation engine MVP
- Weeks 5-6: Study groups feature
- Weeks 7-8: Mentorship matching
- Month 1: React Native setup, migration
- Month 2: Advanced analytics dashboard
- Month 3: Spaced repetition system
- Month 1: Headless CMS integration
- Month 2: Community content workflow
- Month 3: Premium tier launch
Total Estimated Impact:
- User Engagement: +100-150% (from social + gamification)
- Retention: +40-60% (from SRS + recommendations)
- Completion Rate: +30-50% (from personalization)
- Revenue: $50K-200K MRR (from premium + certs + enterprise)
End of Research Document