Erudition Logo

EdUp

Learning Insights & Articles

AI Image Generator thumbnail

Create Your Own AI Image Generator with Gemini and a Few Lines of Code!

July 9, 2025

Ever wanted to build your own AI-powered tool? It's easier than you think! I'm sharing the complete code for a custom image generator...

+

Ever wanted to build your own AI-powered tool? It's easier than you think! I'm sharing the complete code for a custom image generator that you can run and modify directly within Google's Gemini.

This tool provides a clean interface to enter prompts, select aspect ratios, and generate multiple images at once. It even includes a session history and a lightbox for viewing your creations.

Click below to expand and get the full code.

Click to expand and copy the full HTML code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Erudition Design Image Generator</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }
        .loader {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        /* Lightbox styles */
        .lightbox {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.85);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
            padding: 2rem;
            cursor: pointer;
            backdrop-filter: blur(5px);
        }
        .lightbox img {
            max-width: 90%;
            max-height: 85%; /* Make space for download button */
            object-fit: contain;
            border-radius: 0.5rem;
            box-shadow: 0 0 40px rgba(0,0,0,0.5);
            cursor: default;
        }
        .lightbox-close {
            position: absolute;
            top: 20px;
            right: 30px;
            font-size: 3rem;
            color: white;
            cursor: pointer;
            line-height: 1;
            text-shadow: 0 0 5px black;
        }
        .generated-image-container {
            position: relative;
            cursor: pointer;
            transition: transform 0.2s ease-in-out;
            border-radius: 0.5rem; /* rounded-lg */
            overflow: hidden; /* Ensures download button corners are rounded */
        }
        .generated-image-container:hover {
            transform: scale(1.05);
        }
        .history-thumbnail {
            cursor: pointer;
            border-radius: 0.375rem; /* rounded-md */
            border: 2px solid transparent;
            transition: all 0.2s ease-in-out;
            aspect-ratio: 1 / 1;
        }
        .history-thumbnail:hover {
            transform: scale(1.1);
            border-color: #3b82f6; /* blue-500 */
        }
    </style>
</head>
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
    <div class="min-h-screen flex items-center justify-center p-4">
        <div class="w-full max-w-3xl mx-auto bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8">
            <div class="text-center mb-8">
                <img src="https://erudition.ca/images/ED%20logo%20-%20Copy.png" alt="Erudition Design Logo" class="mx-auto h-16 w-auto mb-4 bg-slate-700 p-1 rounded-md">
                <p class="text-gray-600 dark:text-gray-300 mt-2">Generate images with your custom prompts and settings.</p>
            </div>

            <!-- Prompt Input -->
            <div class="mb-6">
                <label for="prompt-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Image Prompt</label>
                <textarea id="prompt-input" rows="4" class="w-full p-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" placeholder="Enter a detailed description..."></textarea>
            </div>

            <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
                <!-- Aspect Ratio Selector -->
                <div>
                    <label for="aspect-ratio-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Aspect Ratio</label>
                    <select id="aspect-ratio-select" class="w-full p-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
                        <option value="1:1">1:1 (Square)</option>
                        <option value="16:9" selected>16:9 (Widescreen)</option>
                        <option value="9:16">9:16 (Portrait)</option>
                        <option value="4:3">4:3 (Standard)</option>
                        <option value="3:4">3:4 (Tall)</option>
                        <option value="2:1">2:1 (Panoramic)</option>
                    </select>
                    <p id="ratio-warning" class="hidden text-xs text-yellow-500 dark:text-yellow-400 mt-2">Note: Panoramic ratios may be less stable and could occasionally result in an error.</p>
                </div>

                <!-- Number of Images Selector -->
                <div>
                    <label for="image-count-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Number of Images</label>
                    <select id="image-count-select" class="w-full p-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
                        <option value="1">1 Image</option>
                        <option value="4">4 Images</option>
                    </select>
                </div>
            </div>

            <!-- Generate Button -->
            <div class="text-center mb-6">
                <button id="generate-btn" class="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg shadow-lg transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800">
                    Generate Image(s)
                </button>
            </div>

            <!-- Image Display Area -->
            <div id="image-container-wrapper" class="mt-6 w-full min-h-[24rem] bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 p-4">
                <div id="placeholder" class="text-center text-gray-500 dark:text-gray-400">
                    <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
                        <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 4v.01M28 8L16 20m12-12v8m0 4v.01M20 28h8m-4-4v8m12-12v8m0 4v.01M28 8L16 20m12-12v8m0 4v.01M20 28h8m-4-4v8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
                    </svg>
                    <p class="mt-2 text-sm">Your generated images will appear here</p>
                </div>
                <div id="loader" class="loader hidden"></div>
                <div id="image-grid" class="hidden w-full h-full grid grid-cols-1 sm:grid-cols-2 gap-4"></div>
            </div>
             <!-- Info/Error Message -->
            <div id="message-area" class="hidden mt-4 text-center p-3 rounded-lg"></div>

            <!-- Session History -->
            <div id="history-section" class="hidden mt-8">
                <h2 class="text-xl font-bold text-center mb-4 text-gray-800 dark:text-white">Session History</h2>
                <div id="history-grid" class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-3">
                    <!-- Thumbnails will be injected here by JavaScript -->
                </div>
            </div>
        </div>
    </div>
    
    <!-- Lightbox Structure -->
    <div id="lightbox" class="lightbox hidden">
        <span id="lightbox-close" class="lightbox-close">×</span>
        <img id="lightbox-img" src="" alt="Enlarged image">
        <!-- Download button for lightbox -->
        <a id="lightbox-download-btn" href="#" download="erudition-design-image.png" class="absolute bottom-5 left-1/2 -translate-x-1/2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 flex items-center gap-2" onclick="event.stopPropagation();">
            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
              <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
            </svg>
            Download
        </a>
    </div>

    <script>
        // --- DOM Element Selection ---
        const generateBtn = document.getElementById('generate-btn');
        const promptInput = document.getElementById('prompt-input');
        const aspectRatioSelect = document.getElementById('aspect-ratio-select');
        const imageCountSelect = document.getElementById('image-count-select');
        
        const placeholder = document.getElementById('placeholder');
        const loader = document.getElementById('loader');
        const imageGrid = document.getElementById('image-grid');
        const messageArea = document.getElementById('message-area');
        const ratioWarning = document.getElementById('ratio-warning');
        
        const lightbox = document.getElementById('lightbox');
        const lightboxImg = document.getElementById('lightbox-img');
        const lightboxClose = document.getElementById('lightbox-close');
        const lightboxDownloadBtn = document.getElementById('lightbox-download-btn');

        const historySection = document.getElementById('history-section');
        const historyGrid = document.getElementById('history-grid');

        // --- Event Listeners ---

        // Lightbox close functionality
        lightbox.addEventListener('click', () => {
            lightbox.classList.add('hidden');
        });
        lightboxClose.addEventListener('click', () => {
            lightbox.classList.add('hidden');
        });

        // Show a warning for potentially unstable aspect ratios
        aspectRatioSelect.addEventListener('change', () => {
            if (aspectRatioSelect.value === '2:1') {
                ratioWarning.classList.remove('hidden');
            } else {
                ratioWarning.classList.add('hidden');
            }
        });

        // Main function to generate images on button click
        generateBtn.addEventListener('click', async () => {
            const prompt = promptInput.value.trim();
            if (!prompt) {
                showMessage('Please enter a prompt.', 'error');
                return;
            }

            // --- UI State Management: Show Loading ---
            setLoadingState(true);
            let response; // To hold the fetch response for better error logging

            try {
                // --- API Payload Preparation ---
                const aspectRatio = aspectRatioSelect.value;
                const sampleCount = parseInt(imageCountSelect.value, 10);
                
                const payload = {
                    instances: [{ prompt: prompt }],
                    parameters: { 
                        "sampleCount": sampleCount,
                        "aspectRatio": aspectRatio
                    }
                };

                // The environment handles the API key automatically.
                const apiKey = ""; 
                const apiUrl = \`https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-002:predict?key=\${apiKey}\`;

                // --- API Call ---
                response = await fetch(apiUrl, {
                    method: 'POST',
                    headers: { 
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(payload)
                });

                if (!response.ok) {
                    // Throw an error to be caught by the catch block
                    throw new Error(\`HTTP error! status: \${response.status}\`);
                }

                const result = await response.json();

                // --- Process and Display Results ---
                if (result.predictions && result.predictions.length > 0) {
                    result.predictions.forEach(prediction => {
                        if (prediction.bytesBase64Encoded) {
                            const imageUrl = \`data:image/png;base64,\${prediction.bytesBase64Encoded}\`;
                            const fileName = \`erudition-design-image-\${Date.now()}.png\`;

                            // --- Create Image Container with Download Button ---
                            const container = document.createElement('div');
                            container.className = 'relative group generated-image-container';

                            const img = document.createElement('img');
                            img.src = imageUrl;
                            img.alt = 'Generated AI Image';
                            img.className = 'w-full h-full object-contain rounded-lg bg-gray-800';
                            
                            const downloadLink = document.createElement('a');
                            downloadLink.href = imageUrl;
                            downloadLink.download = fileName;
                            downloadLink.className = 'absolute bottom-2 right-2 bg-blue-600 text-white p-2 rounded-full shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 hover:bg-blue-700';
                            downloadLink.innerHTML = \`<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>\`;
                            downloadLink.title = 'Download Image';
                            // Prevent lightbox from opening when download button is clicked
                            downloadLink.addEventListener('click', (e) => e.stopPropagation());

                            container.appendChild(img);
                            container.appendChild(downloadLink);
                            
                            // Open lightbox when the container (but not the download button) is clicked
                            container.addEventListener('click', () => {
                                lightboxImg.src = img.src;
                                lightboxDownloadBtn.href = img.src;
                                lightboxDownloadBtn.download = fileName;
                                lightbox.classList.remove('hidden');
                            });

                            imageGrid.appendChild(container);

                            // --- Create and append the history thumbnail ---
                            const thumbnail = document.createElement('img');
                            thumbnail.src = imageUrl;
                            thumbnail.alt = 'History thumbnail';
                            thumbnail.className = 'w-full h-full object-cover history-thumbnail';
                            
                            thumbnail.addEventListener('click', () => {
                                lightboxImg.src = thumbnail.src;
                                lightboxDownloadBtn.href = thumbnail.src;
                                lightboxDownloadBtn.download = fileName;
                                lightbox.classList.remove('hidden');
                            });
                            // Prepend the thumbnail to show the newest first
                            historyGrid.prepend(thumbnail);
                        }
                    });
                    imageGrid.classList.remove('hidden');
                    // Show history section if it has content
                    if (historyGrid.children.length > 0) {
                        historySection.classList.remove('hidden');
                    }
                } else {
                    throw new Error('Invalid response structure from API. No predictions found.');
                }
            } catch (error) {
                // --- Error Handling ---
                console.error('Error generating image:', error);
                let errorMessage = 'An error occurred. Please try again.';
                
                if (aspectRatioSelect.value === '2:1') {
                    errorMessage += ' The 2:1 ratio can sometimes fail. Please try again or select a different ratio.';
                }
                
                if (response) {
                    try {
                        const errorBody = await response.json();
                        console.error("Server error response:", JSON.stringify(errorBody, null, 2));
                        if (errorBody.error && errorBody.error.message) {
                            errorMessage = \`Server Error: \${errorBody.error.message}\`;
                        }
                    } catch (e) {
                        console.error("Could not parse the error response body as JSON.");
                    }
                }
                showMessage(errorMessage, 'error');
                placeholder.classList.remove('hidden');
            } finally {
                // --- UI State Management: Hide Loading ---
                setLoadingState(false);
            }
        });

        // --- Helper Functions ---

        /**
         * Sets the UI to a loading or non-loading state.
         * @param {boolean} isLoading - True to show loader, false to hide.
         */
        function setLoadingState(isLoading) {
            if (isLoading) {
                placeholder.classList.add('hidden');
                imageGrid.classList.add('hidden');
                imageGrid.innerHTML = '';
                messageArea.classList.add('hidden');
                loader.classList.remove('hidden');
                generateBtn.disabled = true;
                generateBtn.classList.add('opacity-50', 'cursor-not-allowed');
            } else {
                loader.classList.add('hidden');
                generateBtn.disabled = false;
                generateBtn.classList.remove('opacity-50', 'cursor-not-allowed');
            }
        }

        /**
         * Displays a message to the user.
         * @param {string} text - The message to display.
         * @param {string} type - 'error' or 'success' for styling.
         */
        function showMessage(text, type = 'error') {
            messageArea.textContent = text;
            const baseClasses = 'mt-4 text-center p-3 rounded-lg';
            if (type === 'error') {
                messageArea.className = \`\${baseClasses} bg-red-100 dark:bg-red-900 text-red-600 dark:text-red-300\`;
            } else if (type === 'success') {
                messageArea.className = \`\${baseClasses} bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-300\`;
            }
            messageArea.classList.remove('hidden');
            
            if (type === 'success') {
                 setTimeout(() => {
                    messageArea.classList.add('hidden');
                }, 3000);
            }
        }
    </script>
</body>
</html>

How to Set It Up in Gemini with Canvas

  1. Open Gemini and Start a New Canvas: In the Gemini interface, look for the option to create a new "Canvas" or interactive environment.
  2. Paste the Code: Simply copy the entire HTML code block above and paste it directly into the Gemini Canvas.
  3. Run the Code: Execute the code within the Canvas. Gemini will render the HTML and create an interactive preview of the image generator.

Important Note: This tool is specifically designed to run within Gemini. It relies on Gemini's backend to securely handle the API key and process the image generation requests. Therefore, it will not work as a standalone webpage.

Easy to Modify and Make Your Own

The best part is that you can easily customize this tool. Here are a few ideas:

  • Change the branding: Swap out the "Erudition Design" logo and text with your own.
  • Adjust the options: Add or remove aspect ratios from the dropdown menu.
  • Set default prompts: Modify the placeholder text in the textarea to suggest different creative ideas.

This is a fantastic way to get hands-on experience with AI and web development without the hassle of setting up a complex development environment. Let me know what you create!

#AI #WebDevelopment #Gemini #Google #GenerativeAI

Gamification thumbnail

It's Not Just a Game, It's the Future of Learning.

July 5, 2025

The concept of gamification in learning isn't new. Using game mechanics like points, challenges, and leaderboards to boost engagement is a proven strategy.

+
Futuristic learning interface with game elements

The concept of gamification in learning isn't new. Using game mechanics like points, challenges, and leaderboards to boost engagement is a proven strategy. However, the true barrier has often been the complexity and resources required to do it well.

That barrier is now coming down, thanks to Artificial Intelligence.

The same AI technology that is revolutionizing countless industries is now making it significantly easier to design and integrate sophisticated, personalized, and truly effective gamified learning experiences.

What does this AI-powered shift mean for your training programs?

Imagine a learning ecosystem where:

  • Dynamic learning paths are automatically customized for each team member based on their performance and learning style, all driven by smart algorithms.
  • Instant, personalized feedback guides learners exactly when they need it, correcting mistakes and reinforcing new skills on the spot.
  • Immersive, complex simulations and branching scenarios can be developed far more efficiently, allowing your team to master real-world skills in a safe, engaging environment.

Simply put, AI is making advanced gamification more accessible and powerful for organizations of all sizes. This allows us to move beyond basic rewards and craft deeply immersive experiences that are not only engaging but also drive measurable results.

At Erudition Design Company, we are harnessing these advancements to redefine what's possible in learning. To give you a taste of this exciting frontier, we've put together a sample exploring the new possibilities within gamified learning.

Ready to see the future in action?

🎮 Explore our gamification sample here: Erudition's Gamification Sample

Customer Education thumbnail

The Untapped ROI: How Customer Education Drives Revenue and Retention

July 3, 2025

Is your support team constantly buried under a mountain of tickets for the same recurring issues? Are you struggling to keep customers engaged and loyal after the initial sale?

+
Business meeting with charts and graphs

Is your support team constantly buried under a mountain of tickets for the same recurring issues? Are you struggling to keep customers engaged and loyal after the initial sale? You're not alone. Many companies face these challenges, but the solution isn't always hiring more support staff. It's about empowering your customers to help themselves. It's about customer education.

The Astonishing Return on Investment

Investing in your customers' success is an investment in your own. It might sound like an expense, but the data proves it's a powerful revenue driver. According to a recent industry report, a staggering 96% of organizations recoup their investment in customer education. Furthermore, 43% of companies with a formal education program report an increase in overall revenue. This isn't just about creating goodwill; it's about building a more profitable business.

The Key to Unlocking Customer Loyalty

In today's competitive market, a great product isn't enough. The experience you provide is what keeps customers coming back. When you educate your customers, you empower them to get the most value from your product, leading to greater satisfaction and loyalty. The impact is significant:

  • Companies with customer education programs see a 56% improvement in customer retention.
  • Product adoption and engagement increase by an average of 38% and 31%, respectively.

Reduce Support Costs and Increase Efficiency

Imagine your expert support team freed from answering basic, repetitive questions. That’s the efficiency customer education unlocks. By providing a library of resources, tutorials, and guides, you enable customers to find answers on their own. This leads to a 16% reduction in support requests on average, allowing your team to focus on the high-impact, complex issues that truly require their expertise.

How to Get Started:

  1. Listen to your customers: Survey your users or talk to your support team to identify the top 3-5 most common questions or friction points.
  2. Start small: You't need a full-blown university on day one. Begin by creating a few simple 'how-to' videos or a short series of onboarding emails.
  3. Measure your impact: Track key metrics like support ticket volume and customer satisfaction scores to demonstrate the value of your efforts.

Customer education is no longer a 'nice-to-have'—it's a core business strategy that drives revenue, boosts retention, and improves operational efficiency. By shifting from a reactive support model to a proactive educational one, you empower your customers and build a stronger, more profitable company.

What's the biggest hurdle your customers face when using your product? Let's discuss in the comments below!

#CustomerEducation #CustomerSuccess #SaaS #BusinessGrowth #ROI #CustomerRetention #CustomerExperience