<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Farm-to-Plate AI Course</title>
<script src=”https://cdn.tailwindcss.com”></script>
<style>
.flip-card {
perspective: 1000px;
height: 200px;
}
.flip-card-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-card.flipped .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
text-align: center;
}
.flip-card-back {
transform: rotateY(180deg);
}
.code-block {
background: #1a1a1a;
color: #00ff00;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
font-family: ‘Courier New’, monospace;
font-size: 0.875rem;
white-space: pre;
}
</style>
</head>
<body class=”bg-gradient-to-br from-green-50 to-blue-50 min-h-screen”>
<div id=”app”></div>
<script>
// Course Data
const courseData = {
1: {
title: “Week 1: Introduction & Context”,
sections: [
{
type: “intro”,
title: “Welcome”,
content: {
heading: “AI for African Smallholder Post-Harvest Management”,
stats: [
“30-50% of produce lost between harvest and market”,
“Billions in annual losses for smallholder farmers”,
“AI can reduce losses by 50%”
]
}
},
{
type: “lesson”,
title: “Post-Harvest Loss Problem”,
content: {
text: “Africa loses 30-50% of produce between harvest and market, costing smallholder farmers billions annually.”,
caseStudy: {
title: “Case Study: Maria’s Tomato Farm”,
current: “Loses 80kg (40%), earns 6,000 KES”,
withAI: “Loses 40kg (20%), earns 8,000 KES”
}
}
},
{
type: “flipcards”,
title: “Root Causes”,
cards: [
{ front: “Physical Losses”, back: “Bruising, rotting, pest damage during handling and transport” },
{ front: “Economic Losses”, back: “Price drops for damaged produce, rejection by buyers” },
{ front: “Infrastructure”, back: “No refrigeration, poor storage facilities” },
{ front: “Information Gap”, back: “Lack of real-time market data and quality tools” }
]
},
{
type: “quiz”,
title: “Week 1 Quiz”,
questions: [
{
q: “What percentage of produce is typically lost in Africa?”,
options: [“10-20%”, “30-50%”, “60-70%”, “80-90%”],
correct: 1
},
{
q: “Where are AI interventions most effective?”,
options: [“Harvest”, “Sorting & Storage”, “Transport”, “Market”],
correct: 1
}
]
}
]
},
2: {
title: “Week 2: Image Data Fundamentals”,
sections: [
{
type: “lesson”,
title: “Digital Images”,
content: {
text: “Understanding how cameras capture produce images.”,
concepts: [
{ term: “Pixels”, def: “Smallest unit of an image” },
{ term: “Resolution”, def: “Number of pixels (e.g., 1920×1080)” },
{ term: “Bit Depth”, def: “Number of bits per pixel (8-bit = 0-255)” }
]
}
},
{
type: “flipcards”,
title: “Color Spaces”,
cards: [
{ front: “RGB”, back: “Red-Green-Blue. Three channels 0-255. Common camera format.” },
{ front: “HSV”, back: “Hue-Saturation-Value. Better for colored defect detection.” },
{ front: “Grayscale”, back: “Single channel. Useful for texture analysis.” }
]
},
{
type: “code”,
title: “MATLAB Lab”,
code: “% Convert to HSV\nimg_rgb = imread(‘tomato.jpg’);\nimg_hsv = rgb2hsv(img_rgb);\n\n% Detect red regions\nH = img_hsv(:,:,1);\nred_mask = (H < 0.05 | H > 0.95);”
},
{
type: “quiz”,
title: “Week 2 Quiz”,
questions: [
{
q: “A 640×480 RGB image is stored as:”,
options: [“640×480 matrix”, “640×480×3 array”, “Three 640×480 matrices”, “Single array”],
correct: 1
}
]
}
]
},
3: {
title: “Week 3: Defect Detection”,
sections: [
{
type: “lesson”,
title: “Common Defects”,
content: {
defects: [
{ type: “Bruising”, impact: “20-40% price reduction” },
{ type: “Rot”, impact: “Rejection by buyers” },
{ type: “Sunburn”, impact: “Lower price tier” }
]
}
},
{
type: “flipcards”,
title: “Segmentation”,
cards: [
{ front: “Global Threshold”, back: “Single value for entire image. Simple but struggles with variable lighting.” },
{ front: “Adaptive Threshold”, back: “Local thresholds. Better for uneven lighting.” },
{ front: “Otsu’s Method”, back: “Automatic threshold selection. No manual tuning.” }
]
},
{
type: “code”,
title: “Detection Pipeline”,
code: “% Defect detection\nimg_gray = rgb2gray(img);\nthreshold = graythresh(img_gray);\nbw = imbinarize(img_gray, threshold);\n\n% Clean with morphology\nse = strel(‘disk’, 5);\nbw_clean = imopen(bw, se);”
},
{
type: “quiz”,
title: “Week 3 Quiz”,
questions: [
{
q: “Which operation removes small noise?”,
options: [“Dilation”, “Opening”, “Closing”, “Threshold”],
correct: 1
}
]
}
]
},
4: {
title: “Week 4: CNN Classification”,
sections: [
{
type: “lesson”,
title: “Grading System”,
content: {
grades: [
{ grade: “A”, desc: “No defects”, price: “100%” },
{ grade: “B”, desc: “Minor defects”, price: “60-80%” },
{ grade: “C”, desc: “Major defects”, price: “0-30%” }
]
}
},
{
type: “flipcards”,
title: “CNN Basics”,
cards: [
{ front: “What are CNNs?”, back: “Deep learning models that automatically learn features from images.” },
{ front: “Transfer Learning”, back: “Use pre-trained models and fine-tune for your task.” },
{ front: “Why CNNs?”, back: “Handle variations, scale well, work with limited data.” }
]
},
{
type: “code”,
title: “CNN Training”,
code: “% Load MobileNet\nnet = mobilenetv2;\n\n% Replace final layer\nlgraph = layerGraph(net);\nnewFC = fullyConnectedLayer(3);\nlgraph = replaceLayer(lgraph, ‘Logits’, newFC);\n\n% Train\nnet_trained = trainNetwork(imds, lgraph, options);”
},
{
type: “quiz”,
title: “Week 4 Quiz”,
questions: [
{
q: “Main advantage of transfer learning?”,
options: [“Faster training”, “Works with limited data”, “Smaller model”, “Better always”],
correct: 1
}
]
}
]
}
};
// State Management
let state = {
currentWeek: 1,
currentSection: 0,
completed: {},
quizAnswers: {},
flipped: {}
};
// Load saved progress
function loadProgress() {
const saved = localStorage.getItem(‘farmToPlateProgress’);
if (saved) {
state = { …state, …JSON.parse(saved) };
}
}
// Save progress
function saveProgress() {
localStorage.setItem(‘farmToPlateProgress’, JSON.stringify(state));
}
// Navigation
function navigate(week, section) {
state.currentWeek = week;
state.currentSection = section;
saveProgress();
render();
}
function nextSection() {
const week = courseData[state.currentWeek];
if (state.currentSection < week.sections.length – 1) {
navigate(state.currentWeek, state.currentSection + 1);
} else if (state.currentWeek < 4) {
navigate(state.currentWeek + 1, 0);
}
}
function prevSection() {
if (state.currentSection > 0) {
navigate(state.currentWeek, state.currentSection – 1);
} else if (state.currentWeek > 1) {
const prevWeek = state.currentWeek – 1;
navigate(prevWeek, courseData[prevWeek].sections.length – 1);
}
}
function markComplete() {
state.completed[`${state.currentWeek}-${state.currentSection}`] = true;
saveProgress();
render();
}
// Render functions
function renderSection(section, idx) {
const key = `${state.currentWeek}-${idx}`;
switch(section.type) {
case ‘intro’:
return `
<div class=”space-y-6″>
<div class=”bg-gradient-to-r from-green-600 to-blue-600 text-white p-8 rounded-lg”>
<h2 class=”text-3xl font-bold mb-4″>${section.content.heading}</h2>
</div>
<div class=”grid md:grid-cols-3 gap-4″>
${section.content.stats.map(stat => `
<div class=”bg-white p-6 rounded-lg shadow-md border-l-4 border-green-500″>
<p class=”text-gray-700″>${stat}</p>
</div>
`).join(”)}
</div>
<button onclick=”markComplete()” class=”w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700″>
Continue
</button>
</div>
`;
case ‘lesson’:
return `
<div class=”space-y-6″>
<div class=”bg-white p-6 rounded-lg shadow-md”>
<p class=”text-lg text-gray-700 mb-4″>${section.content.text}</p>
${section.content.caseStudy ? `
<div class=”bg-blue-50 p-6 rounded-lg mt-6″>
<h4 class=”font-bold text-blue-900 mb-3″>${section.content.caseStudy.title}</h4>
<div class=”grid md:grid-cols-2 gap-4″>
<div class=”bg-red-50 p-4 rounded”>
<p class=”font-semibold text-red-900 mb-2″>Current</p>
<p class=”text-sm”>${section.content.caseStudy.current}</p>
</div>
<div class=”bg-green-50 p-4 rounded”>
<p class=”font-semibold text-green-900 mb-2″>With AI</p>
<p class=”text-sm”>${section.content.caseStudy.withAI}</p>
</div>
</div>
</div>
` : ”}
${section.content.concepts ? `
<div class=”space-y-3 mt-6″>
${section.content.concepts.map(c => `
<div class=”border-l-4 border-blue-500 pl-4 py-2″>
<p class=”font-semibold”>${c.term}</p>
<p class=”text-sm text-gray-600″>${c.def}</p>
</div>
`).join(”)}
</div>
` : ”}
${section.content.defects ? `
<div class=”space-y-3 mt-6″>
${section.content.defects.map(d => `
<div class=”bg-gray-50 p-4 rounded-lg”>
<p class=”font-bold”>${d.type}</p>
<p class=”text-sm text-red-600″>${d.impact}</p>
</div>
`).join(”)}
</div>
` : ”}
${section.content.grades ? `
<div class=”space-y-3 mt-6″>
${section.content.grades.map(g => `
<div class=”bg-gray-50 p-4 rounded-lg border-2 border-gray-200″>
<p class=”font-bold”>Grade ${g.grade}</p>
<p class=”text-sm text-gray-600″>${g.desc}</p>
<p class=”text-sm text-green-600 font-semibold”>${g.price}</p>
</div>
`).join(”)}
</div>
` : ”}
</div>
<button onclick=”markComplete()” class=”w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700″>
Continue
</button>
</div>
`;
case ‘flipcards’:
return `
<div class=”space-y-6″>
<div class=”bg-blue-50 p-4 rounded-lg”>
<p class=”text-center text-blue-900″>Click each card to flip</p>
</div>
<div class=”grid md:grid-cols-2 gap-6″>
${section.cards.map((card, i) => `
<div class=”flip-card ${state.flipped[`${key}-${i}`] ? ‘flipped’ : ”}”
onclick=”flipCard(‘${key}-${i}’)”>
<div class=”flip-card-inner”>
<div class=”flip-card-front bg-gradient-to-br from-green-500 to-green-600 text-white”>
<div>
<p class=”text-xl font-bold”>${card.front}</p>
<p class=”text-sm mt-2 opacity-75″>Click to flip</p>
</div>
</div>
<div class=”flip-card-back bg-gradient-to-br from-blue-500 to-blue-600 text-white”>
<p class=”text-sm”>${card.back}</p>
</div>
</div>
</div>
`).join(”)}
</div>
<button onclick=”markComplete()” class=”w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700″>
Continue
</button>
</div>
`;
case ‘code’:
return `
<div class=”space-y-6″>
<div class=”bg-white p-6 rounded-lg shadow-md”>
<h4 class=”font-bold text-lg mb-4″>💻 MATLAB Code Example</h4>
<div class=”code-block”>${section.code}</div>
</div>
<button onclick=”markComplete()” class=”w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700″>
Mark Complete
</button>
</div>
`;
case ‘quiz’:
return renderQuiz(section.questions, key);
}
}
function renderQuiz(questions, key) {
const answers = state.quizAnswers[key] || {};
const submitted = state.completed[key];
let html = ‘<div class=”space-y-6″>’;
questions.forEach((q, qIdx) => {
html += `
<div class=”bg-white p-6 rounded-lg shadow-md”>
<p class=”font-semibold mb-4″>${qIdx + 1}. ${q.q}</p>
<div class=”space-y-2″>
${q.options.map((opt, oIdx) => {
const selected = answers[qIdx] === oIdx;
const correct = oIdx === q.correct;
const showFeedback = submitted && selected;
return `
<button onclick=”answerQuiz(‘${key}’, ${qIdx}, ${oIdx})”
${submitted ? ‘disabled’ : ”}
class=”w-full text-left p-3 rounded-lg border-2 transition-all ${
showFeedback
? (correct ? ‘border-green-500 bg-green-50’ : ‘border-red-500 bg-red-50’)
: (selected ? ‘border-blue-500 bg-blue-50’ : ‘border-gray-200 hover:border-gray-300’)
}”>
${opt}
${showFeedback ? (correct ? ‘ ✓’ : ‘ ✗’) : ”}
</button>
`;
}).join(”)}
</div>
</div>
`;
});
if (!submitted) {
const allAnswered = Object.keys(answers).length === questions.length;
html += `
<button onclick=”submitQuiz(‘${key}’, ${questions.length})”
${!allAnswered ? ‘disabled’ : ”}
class=”w-full bg-green-600 text-white py-3 rounded-lg font-semibold hover:bg-green-700 disabled:bg-gray-300″>
Submit Quiz
</button>
`;
} else {
const score = questions.reduce((acc, q, i) => acc + (answers[i] === q.correct ? 1 : 0), 0);
html += `
<div class=”bg-blue-50 p-6 rounded-lg text-center”>
<p class=”text-2xl font-bold text-blue-900″>Score: ${score}/${questions.length}</p>
<p class=”text-gray-600 mt-2″>${score === questions.length ? ‘Perfect! 🎉’ : ‘Great job! 👏’}</p>
</div>
`;
}
html += ‘</div>’;
return html;
}
function flipCard(cardId) {
state.flipped[cardId] = !state.flipped[cardId];
render();
}
function answerQuiz(key, qIdx, answer) {
if (!state.quizAnswers[key]) state.quizAnswers[key] = {};
state.quizAnswers[key][qIdx] = answer;
saveProgress();
render();
}
function submitQuiz(key, numQuestions) {
markComplete();
}
function render() {
const week = courseData[state.currentWeek];
const section = week.sections[state.currentSection];
const progress = (Object.keys(state.completed).length / 16) * 100;
document.getElementById(‘app’).innerHTML = `
<div class=”max-w-6xl mx-auto px-4 py-6″>
<!– Header –>
<div class=”bg-white shadow-md rounded-lg p-4 mb-6″>
<div class=”flex items-center justify-between mb-2″>
<h1 class=”text-2xl font-bold text-gray-900″>Farm-to-Plate AI</h1>
<span class=”text-sm font-semibold text-gray-600″>Week ${state.currentWeek} of 4</span>
</div>
<div class=”mt-3″>
<div class=”flex items-center justify-between text-sm text-gray-600 mb-1″>
<span>Progress</span>
<span>${Math.round(progress)}%</span>
</div>
<div class=”w-full bg-gray-200 rounded-full h-3″>
<div class=”bg-gradient-to-r from-green-500 to-blue-500 h-3 rounded-full transition-all”
style=”width: ${progress}%”></div>
</div>
</div>
</div>
<!– Week Tabs –>
<div class=”grid grid-cols-4 gap-4 mb-6″>
${[1,2,3,4].map(w => `
<button onclick=”navigate(${w}, 0)”
class=”p-4 rounded-lg font-semibold ${state.currentWeek === w ? ‘bg-green-600 text-white’ : ‘bg-white text-gray-600 hover:bg-gray-50’}”>
Week ${w}
</button>
`).join(”)}
</div>
<!– Content –>
<div class=”bg-white rounded-lg shadow-xl p-8 mb-6″>
<div class=”mb-6″>
<h2 class=”text-2xl font-bold text-gray-900 mb-2″>${week.title}</h2>
<p class=”text-gray-600″>Section ${state.currentSection + 1} of ${week.sections.length}: ${section.title}</p>
</div>
${renderSection(section, state.currentSection)}
</div>
<!– Navigation –>
<div class=”flex justify-between”>
<button onclick=”prevSection()”
${state.currentWeek === 1 && state.currentSection === 0 ? ‘disabled’ : ”}
class=”px-6 py-3 bg-gray-600 text-white rounded-lg font-semibold hover:bg-gray-700 disabled:bg-gray-300″>
← Previous
</button>
<button onclick=”nextSection()”
${state.currentWeek === 4 && state.currentSection === week.sections.length – 1 ? ‘disabled’ : ”}
class=”px-6 py-3 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 disabled:bg-gray-300″>
Next →
</button>
</div>
</div>
`;
}
// Initialize
loadProgress();
render();
</script>
</body>
</html>