Farm-to-Plate AI Course

<!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>

Leave a Reply

Your email address will not be published. Required fields are marked *