potato ai official 1.0 (corbun does weird webs upd )

This sketch creates a playful chat interface where users converse with "Potato AI" (Jeff), a quirky potato-themed chatbot that responds to messages, asks conversational questions, and occasionally displays potato-themed images or bizarre Easter eggs.

🧪 Try This!

Experiment with the code by making these changes:

  1. Add a new potato question
  2. Change the AI's name — The AI is called 'Jeff' everywhere—change it to any name you want
  3. Make the AI respond to a new keyword — Add a keyword detection that triggers a custom response—e.g., 'hello' could become 'yo'
  4. Speed up the thinking delay — The AI waits 1.5 seconds before responding—reduce it to 500ms for a snappier feel
  5. Change the user message color — User messages are light green (#dcf8c6)—try a different hex color like light blue or yellow
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a working chat application with a potato twist—you type messages and converse with Potato AI, a character that responds with quirky potato-themed quips, asks you questions from a curated list, and occasionally surprises you with easter eggs. It uses p5.js's createDiv(), createInput(), and createButton() to build a responsive HTML interface without drawing on a canvas, teaching you how p5.js can construct interactive web applications beyond visual sketching.

The code is organized around three main functions: setup() creates the chat interface with input controls, sendMessage() handles user input and displays a thinking delay, and generateResponse() contains all the conversational logic with keyword detection and random response selection. By studying it, you'll learn how to build responsive text-based applications, manage event listeners, handle asynchronous delays with setTimeout(), and use conditional logic to create personality and unpredictability in your interactions.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen chat interface using flexbox styling: a header with the title, a scrollable chat container in the middle, and an input area at the bottom with a text field and Send button.
  2. The user types a message and presses Send or Enter, triggering sendMessage() which adds their message to the chat and clears the input field.
  3. A 'thinking' image of a potato appears for 1.5 seconds to simulate the AI processing, adding a fun delay before the response.
  4. generateResponse() analyzes the user's message with toLowerCase() and keyword detection—checking for triggers like 'hello', 'code', 'fries', or special phrases like 'pst I see dead people'.
  5. Based on the keywords found, the AI returns a text response, an image, a repeating image sequence, or even triggers a 1% chance easter egg that fills the screen with a dark background and an exploding potato GIF.
  6. Each response is added to the chat with addMessage(), which styles it differently based on sender (user messages are green and right-aligned, AI messages are white and left-aligned with 'Jeff:' prefix).
  7. The chat automatically scrolls to show the newest messages, maintaining conversation flow.

🎓 Concepts You'll Learn

HTML element creation (createDiv, createInput, createButton)Event listeners and callbacks (mousePressed, keypress)Conditional logic and keyword detectionAsynchronous timing (setTimeout)String methods (toLowerCase, includes)CSS flexbox stylingObject types and type checking

📝 Code Breakdown

setup()

setup() runs once at the start of the sketch. Here, we build the entire chat UI using p5.js's HTML creation functions (createDiv, createInput, createButton) and CSS styling. Notice how .parent() connects elements to containers, creating a hierarchy. The keypressListener is stored in a variable so we can remove it later when the dark screen easter egg triggers—this teaches you that event listeners must be managed carefully.

function setup() {
  noCanvas();

  // --- Create Main Flex Container ---
  mainContainer = createDiv();
  mainContainer.style("display", "flex");
  mainContainer.style("flex-direction", "column");
  mainContainer.style("height", "100vh"); // Make it full height

  // --- Create the Title Element ---
  titleElement = createElement('h1', 'POTATO AI 1.0🥔'); // Updated title to potato theme
  titleElement.style("text-align", "center");
  titleElement.style("padding", "10px");
  titleElement.style("margin", "0");
  titleElement.style("background-color", "#f8f8f8");
  titleElement.style("border-bottom", "1px solid #ddd");
  titleElement.parent(mainContainer);

  chatContainer = createDiv();
  chatContainer.style("flex-grow", "1"); // Make chatContainer fill available space
  chatContainer.style("overflow-y", "auto");
  chatContainer.style("padding", "10px");
  chatContainer.style("display", "flex");
  chatContainer.style("flex-direction", "column");
  chatContainer.parent(mainContainer); // Parent to mainContainer

  inputArea = createDiv(); // Declared globally
  inputArea.style("display", "flex");
  inputArea.style("padding", "10px");
  inputArea.style("background-color", "#f0f0f0");
  inputArea.parent(mainContainer); // Parent to mainContainer

  inputBox = createInput();
  inputBox.style("flex", "1");
  inputBox.style("padding", "10px");
  inputBox.style("font-size", "16px");
  inputBox.parent(inputArea);

  sendButton = createButton("Send");
  sendButton.style("padding", "10px 20px");
  sendButton.style("margin-left", "5px");
  sendButton.parent(inputArea);

  sendButton.mousePressed(sendMessage);

  // Store the listener function
  keypressListener = function(e) {
    if (e.key === "Enter") sendMessage();
  };
  inputBox.elt.addEventListener("keypress", keypressListener);

  // --- Add the Exit Button ---
  let exitButton = createButton('X');
  exitButton.style('position', 'fixed');
  exitButton.style('top', '10px');
  exitButton.style('right', '10px');
  exitButton.style('padding', '10px 15px');
  exitButton.style('font-size', '18px');
  exitButton.style('background-color', '#ff0000'); // Red for exit
  exitButton.style('color', 'white');
  exitButton.style('border', 'none');
  exitButton.style('border-radius', '5px');
  exitButton.style('cursor', 'pointer');
  exitButton.style('z-index', '1000'); // Ensure it's on top of other elements
  exitButton.mousePressed(() => {
    window.open('https://www.canva.com/design/DAHBhUmxxCY/xn8SvJ7_G7hOoDC68lXviw/view', '_blank'); // Open in a new tab
  });
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

function-call Disable canvas noCanvas();

Tells p5.js not to create a drawing canvas—this sketch uses HTML elements instead

element-creation Main flex container mainContainer = createDiv(); mainContainer.style("display", "flex"); mainContainer.style("flex-direction", "column"); mainContainer.style("height", "100vh");

Creates the outermost container that stacks the title, chat area, and input field vertically and fills the entire viewport

event-listener Enter key listener keypressListener = function(e) { if (e.key === "Enter") sendMessage(); }; inputBox.elt.addEventListener("keypress", keypressListener);

Allows users to press Enter to send a message instead of clicking the button; stores the listener function so it can be removed later

element-creation Exit button with click handler exitButton.mousePressed(() => { window.open('https://www.canva.com/design/DAHBhUmxxCY/xn8SvJ7_G7hOoDC68lXviw/view', '_blank'); // Open in a new tab });

Creates a fixed red X button that opens an external link when clicked

noCanvas();
Tells p5.js to skip creating a canvas—this sketch is purely HTML-based, so we don't need to draw pixels
mainContainer = createDiv();
Creates a new HTML div element that will hold all other UI elements
mainContainer.style("display", "flex");
Enables flexbox layout on the main container so child elements stack and align easily
mainContainer.style("flex-direction", "column");
Stacks child elements vertically (column) instead of side-by-side
mainContainer.style("height", "100vh");
Sets the container height to 100% of the viewport, making it fill the entire screen
titleElement = createElement('h1', 'POTATO AI 1.0🥔');
Creates an h1 heading element with the title text; createElement() is a p5.js function to make HTML tags
titleElement.parent(mainContainer);
Adds the title as a child of mainContainer—it will appear inside and be organized by the flex layout
chatContainer.style("flex-grow", "1");
Tells the chat container to expand and fill all available vertical space between the title and input area
chatContainer.style("overflow-y", "auto");
Allows the chat area to scroll vertically if there are many messages, preventing content overflow
inputBox = createInput();
Creates an HTML text input field where users can type their messages
inputBox.style("flex", "1");
Tells the input field to expand horizontally and take up all available space in the input area
sendButton = createButton("Send");
Creates a clickable button labeled 'Send'
sendButton.mousePressed(sendMessage);
Connects the button to the sendMessage() function—when clicked, sendMessage runs
keypressListener = function(e) { if (e.key === "Enter") sendMessage(); };
Defines a function that listens for keyboard events and calls sendMessage() if the Enter key is pressed; storing it in a variable lets us remove it later if needed
inputBox.elt.addEventListener("keypress", keypressListener);
Attaches the keypressListener to the input field so pressing Enter while typing will send the message
exitButton.style('z-index', '1000');
Sets a very high z-index (stacking order) so the exit button always appears on top of other elements like the dark screen GIF

sendMessage()

sendMessage() ties user input to AI response. It demonstrates several key patterns: validating input with trim() and early return, using setTimeout() for asynchronous delays (teaching that code doesn't always run instantly), managing element references globally so we can clean them up, and using random() to add unpredictability. This function shows how real chat applications balance immediate user feedback (showing their message right away) with simulated 'thinking' delays (making the AI feel responsive but not instant).

function sendMessage() {
  let message = inputBox.value();
  if (message.trim() === "") return;

  addMessage(message, "user");
  inputBox.value("");

  // Create and display thinking indicator with updated image and size
  thinkingCowImageElement = createImg("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6H5014x2cM_G6Q3C3iR7Q9C2m7qX2j0S8kQ&s", "Jeff thinking"); // New potato thinking image
  thinkingCowImageElement.style("width", "100px"); // Increased width
  thinkingCowImageElement.style("height", "auto"); // Maintain aspect ratio
  thinkingCowImageElement.style("margin", "5px 0");
  thinkingCowImageElement.style("align-self", "flex-start"); // Align like an AI message
  thinkingCowImageElement.style("border-radius", "10px"); // Soften edges
  thinkingCowImageElement.parent(chatContainer);
  thinkingCowImageElement.elt.scrollIntoView({ behavior: 'smooth', block: 'end' });

  // Reintroduce thinking delay (1.5 seconds)
  setTimeout(() => {
    // Remove thinking indicator
    if (thinkingCowImageElement) {
      thinkingCowImageElement.remove();
      thinkingCowImageElement = null; // Clear the reference
    }

    // Introduce a 1% chance for the screen to go dark and display the GIF
    if (random() < 0.01) { // 1% chance
      addMessage({ type: "darkScreen", gifSrc: "https://media.giphy.com/media/l4pT02uT7bH66p5wQ/giphy.gif" }, "ai"); // New exploding potato GIF
      return; // Stop processing other responses
    }

    let reply = generateResponse(message);
    addMessage(reply, "ai"); // Jeff's name will be added inside addMessage
  }, 1500); // 1.5 second thinking delay
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Empty message guard if (message.trim() === "") return;

Prevents empty or whitespace-only messages from being sent, keeping the chat clean

element-creation Thinking image display thinkingCowImageElement = createImg("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6H5014x2cM_G6Q3C3iR7Q9C2m7qX2j0S8kQ&s", "Jeff thinking");

Creates and displays an image that represents the AI thinking, adding anticipation before the response

async-timing Async response delay setTimeout(() => { // Remove thinking indicator if (thinkingCowImageElement) { thinkingCowImageElement.remove(); thinkingCowImageElement = null; } // ... }, 1500);

Delays the response by 1.5 seconds using setTimeout, creating a human-like thinking pause before the AI responds

conditional Easter egg trigger if (random() < 0.01) { // 1% chance addMessage({ type: "darkScreen", gifSrc: "https://media.giphy.com/media/l4pT02uT7bH66p5wQ/giphy.gif" }, "ai"); return; }

1% chance of triggering a special dark screen easter egg instead of a normal response

let message = inputBox.value();
Retrieves whatever text the user typed in the input field and stores it in the message variable
if (message.trim() === "") return;
trim() removes leading/trailing spaces; if the message is empty after trimming, exit early to prevent sending blank messages
addMessage(message, "user");
Adds the user's message to the chat display with 'user' styling (green, right-aligned)
inputBox.value("");
Clears the input field so the user can type their next message
thinkingCowImageElement = createImg(..., "Jeff thinking");
Creates an image element from a URL and stores it in a global variable so we can remove it later
thinkingCowImageElement.parent(chatContainer);
Adds the thinking image to the chat so it displays immediately after the user message
thinkingCowImageElement.elt.scrollIntoView({ behavior: 'smooth', block: 'end' });
Smoothly scrolls the chat down so the new thinking image is visible on screen
setTimeout(() => { ... }, 1500);
Delays everything inside the arrow function by 1500 milliseconds (1.5 seconds), simulating the AI's thinking time
if (thinkingCowImageElement) { thinkingCowImageElement.remove(); thinkingCowImageElement = null; }
Removes the thinking image from the screen and sets its reference to null to free memory
if (random() < 0.01) { // 1% chance
random() returns a number 0–1; if it's less than 0.01, we have a 1% chance, and this code runs
let reply = generateResponse(message);
Calls generateResponse() with the user's message to get an AI response—this is where the keyword logic happens
addMessage(reply, "ai");
Adds the AI's response to the chat with 'ai' styling (white, left-aligned, prefixed with 'Jeff:')

generateResponse(message)

generateResponse() is the heart of the chatbot's personality. It demonstrates a critical pattern in conditional logic: checking specific cases first (greetings, easter eggs, keywords), then general patterns (coding requests, long text), then random chances (15% question, 25% off-script). The order of conditionals matters—the 'pst' check must come before the simpler 'i see dead people' check, or the priority rule breaks. Notice how different conditions return different types: strings for text, objects with 'type' fields for special handling. This teaches you that functions can return different data shapes, and the caller (addMessage) checks the type to decide what to do.

🔬 This keyword check has four conditions separated by ||. What happens if you add another keyword like 'cheese' or 'pizza'? Add your own trigger!

  // --- NEW: Display beef/steak image (now fries) ---
  if (lower.includes("beef") || lower.includes("steak") || lower.includes("fries") || lower.includes("potato")) { // Added fries/potato detection
    return { type: "image", src: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_xN8E3J01y0m8a9V4p1Q9V2q1A5V8k2D7Q&s" }; // New fries image
  }
function generateResponse(message) {
  let lower = message.toLowerCase();

  // --- Jeff says hi if user says hello or hi ---
  if (lower.includes("hello") || lower.includes("hi")) {
    return "Hi I'm Jeff"; // Updated name
  }

  // --- Handle "pst I see dead people" (takes priority) ---
  if (lower.includes("pst i see dead people")) {
    return "Whoa, that's a chilling secret to share, gang."; // Jeff's response without the image
  }

  // --- Display "I see dead people" image 10 times (only if "pst" is NOT present) ---
  if (lower.includes("i see dead people")) {
    return {
      type: "repeatingImage",
      src: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7y2l89m3D2H4Y7a9D13J8N2Q1a7J2i1G1vQ&s", // New creepy potato image
      count: 10
    };
  }

  // --- If player says "67", Jeff says "67" back ---
  if (lower === "67") {
    return "67";
  }

  // --- NEW: Display beef/steak image (now fries) ---
  if (lower.includes("beef") || lower.includes("steak") || lower.includes("fries") || lower.includes("potato")) { // Added fries/potato detection
    return { type: "image", src: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_xN8E3J01y0m8a9V4p1Q9V2q1A5V8k2D7Q&s" }; // New fries image
  }

  // --- Existing logic for contextual responses ---

  // Detect coding/scripting requests
  if (
    lower.includes("code") ||
    lower.includes("script") ||
    lower.includes("program") ||
    lower.includes("function")
  ) {
    return "I have no Idea bro I cant code";
  }

  // Detect reading/summarizing/explaining requests
  if (
    lower.includes("read") ||
    lower.includes("summarize") ||
    lower.includes("summary") ||
    lower.includes("explain") ||
    lower.includes("analyze") ||
    lower.length > 200   // long pasted paragraph
  ) {
    return "I aint reading all that gang";
  }

  // Introduce a chance for Jeff to ask a question (e.g., 15% chance)
  if (random() < 0.15) {
    let questionToAsk;
    if (bobQuestions.length > 0) { // Using bobQuestions (now jeffQuestions) array
      let tries = 0;
      do {
        questionToAsk = random(bobQuestions);
        tries++;
      } while (questionToAsk === lastQuestionAsked && tries < 10); // Try up to 10 times to avoid repetition
      lastQuestionAsked = questionToAsk; // Store the actual question string
    } else {
      questionToAsk = "What's on your mind?"; // Fallback if no questions defined
    }
    return questionToAsk;
  }

  // --- Random responses (will only happen if no specific condition met) ---

  // Define some random off-script phrases
  let offScriptPhrases = [
    "... can you say that again bro I zoned out, I was thinking about fries 🍟", // Potato-themed
    "I don't know, can you peel yourself?", // Potato-themed
    "What was the question again? My brain is full of starch 🥔", // Potato-themed
    "Oops, my skin is getting wrinkly, gotta go!", // Potato-themed
    "Boil 'em, mash 'em, stick 'em in a stew...", // Potato-themed (Lord of the Rings reference)
    "Did you hear that? I think a sprout just grew on my skin.", // Potato-themed
    "Is it fry time yet?" // Potato-themed
  ];

  // Introduce a random chance for Jeff to go off-script (25% chance)
  if (random() < 0.25) {
    let phrase = random(offScriptPhrases);
    return phrase; // Pick a random phrase from the list
  }

  return "I don't know ¯\_(ツ)_/¯";
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Greeting detection if (lower.includes("hello") || lower.includes("hi")) { return "Hi I'm Jeff"; }

Recognizes when the user greets the AI and responds with a friendly greeting

conditional Priority easter egg (pst phrase) if (lower.includes("pst i see dead people")) { return "Whoa, that's a chilling secret to share, gang."; }

A specific phrase takes priority over similar patterns to prevent unintended behavior

conditional Repeating image trigger if (lower.includes("i see dead people")) { return { type: "repeatingImage", src: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7y2l89m3D2H4Y7a9D13J8N2Q1a7J2i1G1vQ&s", count: 10 }; }

Returns an object instead of a string—addMessage() checks the type and displays 10 images with delays

conditional Keyword detection (fries, beef, potato) if (lower.includes("beef") || lower.includes("steak") || lower.includes("fries") || lower.includes("potato")) { return { type: "image", src: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_xN8E3J01y0m8a9V4p1Q9V2q1A5V8k2D7Q&s" }; }

Detects food-related keywords and responds with an image instead of text

conditional Code request refusal if ( lower.includes("code") || lower.includes("script") || lower.includes("program") || lower.includes("function") ) { return "I have no Idea bro I cant code"; }

Recognizes coding requests and gives a humorous refusal

conditional Reading/explaining refusal if ( lower.includes("read") || lower.includes("summarize") || lower.includes("summary") || lower.includes("explain") || lower.includes("analyze") || lower.length > 200 ) { return "I aint reading all that gang"; }

Refuses to read long text or do complex analysis; also checks message length to catch pasted paragraphs

conditional-with-loop AI asks a question (15% chance) if (random() < 0.15) { let questionToAsk; if (bobQuestions.length > 0) { let tries = 0; do { questionToAsk = random(bobQuestions); tries++; } while (questionToAsk === lastQuestionAsked && tries < 10); lastQuestionAsked = questionToAsk; } else { questionToAsk = "What's on your mind?"; } return questionToAsk; }

15% chance to ask a question from the bobQuestions array, avoiding immediate repetition by trying up to 10 times to pick a different one

conditional Off-script random response (25% chance) if (random() < 0.25) { let phrase = random(offScriptPhrases); return phrase; }

25% chance to return a quirky random phrase instead of a generic response, adding personality

let lower = message.toLowerCase();
Converts the entire message to lowercase so keyword detection works whether the user types 'Hi', 'HI', or 'hi'
if (lower.includes("hello") || lower.includes("hi")) {
Checks if the message contains 'hello' OR 'hi'—if either is true, the greeting response runs
if (lower.includes("pst i see dead people")) {
Checks for this exact multi-word phrase; it must come BEFORE the simpler 'i see dead people' check so this priority rule triggers first
return { type: "repeatingImage", src: "...", count: 10 };
Instead of returning a string, this returns an object with a special 'type' field; addMessage() reads this and knows to display 10 images with delays
if (lower === "67") {
Uses === (exact match) instead of includes(), so only the exact string '67' triggers this response
if (random() < 0.15) {
random() picks a number 0–1; if it's less than 0.15, there's a 15% chance this code runs
do { questionToAsk = random(bobQuestions); tries++; } while (questionToAsk === lastQuestionAsked && tries < 10);
A do-while loop that picks a random question, increments tries, and repeats if the question matches the last one asked (up to 10 attempts)—ensures variety
lastQuestionAsked = questionToAsk;
Stores the question that was just picked so the next time this code runs, it can avoid repeating it
let offScriptPhrases = [ ... ];
Defines an array of quirky responses that Jeff can randomly choose from
if (random() < 0.25) { ... }
25% chance to pick a random off-script phrase instead of the generic fallback
return "I don't know ¯\_(ツ)_/¯";
The final fallback response if no other conditions matched—this keeps the AI from breaking if it doesn't recognize the input

addMessage(content, sender)

addMessage() is the engine that renders all chat content. It handles three completely different cases: the dark screen easter egg (modifies the DOM globally), repeating images (uses recursion and setTimeout), and normal messages (creates styled bubbles). The type-checking pattern (typeof content === "object" && content.type === "...") is powerful—it lets functions return different data shapes and callers decide how to handle each. Notice how the function uses return early to stop processing when it handles a special case. This function also teaches you about DOM manipulation: document.body.style.backgroundColor directly modifies the page, and removeEventListener cleans up previous listeners.

🔬 This code styles user messages (green, right) and AI messages (white, left). What happens if you swap the align-self values—put 'flex-end' on the AI side and 'flex-start' on the user side?

  if (sender === "user") {
    bubble.style("background-color", "#dcf8c6");
    bubble.style("align-self", "flex-end");
  } else { // This will now be for 'ai' messages
    bubble.style("background-color", "white");
    bubble.style("align-self", "flex-start");
  }
function addMessage(content, sender) {
  // Handle the special "darkScreen" event
  if (typeof content === "object" && content.type === "darkScreen") {
    document.body.style.backgroundColor = "black";

    // Hide the entire chat interface (main flex container)
    mainContainer.style("display", "none");

    // Create a temporary container for the GIF directly in the body
    let tempGifContainer = createDiv();
    tempGifContainer.style("position", "fixed");
    tempGifContainer.style("top", "0");
    tempGifContainer.style("left", "0");
    tempGifContainer.style("width", "100vw");
    tempGifContainer.style("height", "100vh");
    tempGifContainer.style("display", "flex");
    tempGifContainer.style("justify-content", "center");
    tempGifContainer.style("align-items", "center");
    tempGifContainer.style("z-index", "999"); // Below exit button, but above everything else
    tempGifContainer.parent(document.body);

    let gif = createImg(content.gifSrc, "Exploding Potato"); // Updated alt text
    gif.style("max-width", "90%");
    gif.style("max-height", "90%");
    gif.style("width", "auto");
    gif.style("height", "auto");
    gif.parent(tempGifContainer);

    // Remove the keypress listener so Enter key doesn't work
    inputBox.elt.removeEventListener("keypress", keypressListener);

    return; // Stop further processing for this message
  }

  // Handle repeating image trigger
  if (typeof content === "object" && content.type === "repeatingImage") {
    const imageUrl = content.src;
    const totalCount = content.count;
    let currentCount = 0;

    function addRepeatingImage() {
        if (currentCount < totalCount) {
            // Recursively call addMessage to display a single image bubble
            addMessage({ type: "image", src: imageUrl }, "ai");
            currentCount++;
            setTimeout(addRepeatingImage, 750); // Delay before adding the next (0.75 seconds)
        }
    }
    addRepeatingImage(); // Start the first one
    return; // Stop further processing for this message
  }

  // --- Regular message/image handling ---
  let bubble = createDiv(); // Create the bubble container
  bubble.style("padding", "10px");
  bubble.style("margin", "5px 0");
  bubble.style("border-radius", "15px");
  bubble.style("max-width", "60%");
  bubble.style("word-wrap", "break-word"); // Ensure text wraps

  // Determine sender styling
  if (sender === "user") {
    bubble.style("background-color", "#dcf8c6");
    bubble.style("align-self", "flex-end");
  } else { // This will now be for 'ai' messages
    bubble.style("background-color", "white");
    bubble.style("align-self", "flex-start");
  }

  // Prepend Jeff's name if the sender is 'ai'
  let prefix = "";
  if (sender === "ai") {
    prefix = "Jeff: "; // Updated name
  }

  // Check if content is an image object or text
  if (typeof content === "object" && content.type === "image") {
    // If there's a prefix (Jeff's name), add it as a separate text node
    if (prefix) {
      let prefixSpan = createSpan(prefix);
      prefixSpan.parent(bubble);
    }

    let img = createImg(content.src, "potato meme image"); // Create p5.js image element
    img.style("max-width", "100%"); // Ensure image fits within the bubble
    img.style("height", "auto"); // Maintain aspect ratio
    img.style("display", "block"); // Remove extra space below image
    img.style("margin-top", "5px"); // Add a little space between prefix and image
    img.parent(bubble); // Add the image to the bubble
  } else {
    // It's text content
    bubble.html(prefix + content); // Set the HTML content directly
  }

  bubble.parent(chatContainer);
  // Scroll the newly added message bubble into view with smooth animation
  bubble.elt.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

conditional Dark screen easter egg handler if (typeof content === "object" && content.type === "darkScreen") { ... }

Detects and handles the special dark screen easter egg by hiding the chat and displaying a full-screen GIF

conditional Repeating image handler if (typeof content === "object" && content.type === "repeatingImage") { ... }

Detects repeating image requests and adds them with delays using recursion and setTimeout

element-creation Message bubble styling let bubble = createDiv(); bubble.style("padding", "10px"); bubble.style("margin", "5px 0"); bubble.style("border-radius", "15px"); bubble.style("max-width", "60%");

Creates a styled container (chat bubble) for each message

conditional Sender-specific styling if (sender === "user") { bubble.style("background-color", "#dcf8c6"); bubble.style("align-self", "flex-end"); } else { bubble.style("background-color", "white"); bubble.style("align-self", "flex-start"); }

Styles user messages green and right-aligned, AI messages white and left-aligned

conditional Image vs text content if (typeof content === "object" && content.type === "image") { ... } else { ... }

Checks whether the content is an image object or plain text and renders accordingly

if (typeof content === "object" && content.type === "darkScreen") {
Checks if content is an object AND has a 'type' field equal to 'darkScreen'—this protects against errors if content is just a string
document.body.style.backgroundColor = "black";
Directly accesses the HTML body element and sets its background to black, creating the dark screen effect
mainContainer.style("display", "none");
Hides the entire chat interface by setting display to 'none', revealing the black background
tempGifContainer.style("z-index", "999");
Sets a high z-index so the GIF appears on top of other elements (but the exit button has 1000, so it stays clickable)
inputBox.elt.removeEventListener("keypress", keypressListener);
Removes the keypress listener we added in setup() so Enter key no longer works—the page is now 'locked' until the user closes it
if (typeof content === "object" && content.type === "repeatingImage") {
Similar type-checking pattern: detects repeating image requests
function addRepeatingImage() { if (currentCount < totalCount) { addMessage({ type: "image", src: imageUrl }, "ai"); currentCount++; setTimeout(addRepeatingImage, 750); } }
A recursive function that calls itself via setTimeout with a 750ms delay—each call adds one image, increments the counter, and schedules the next call
let bubble = createDiv();
Creates a new div to serve as the chat bubble container
bubble.style("word-wrap", "break-word");
Ensures long words in messages wrap to the next line instead of overflowing
if (sender === "user") { ... } else { ... }
Different styling for user vs AI messages: green/right for user, white/left for AI
let prefix = ""; if (sender === "ai") { prefix = "Jeff: "; }
Adds 'Jeff: ' prefix only to AI messages so users know who's speaking
if (typeof content === "object" && content.type === "image") { ... } else { ... }
Branches to handle image content differently from text content
bubble.html(prefix + content);
For text content, concatenates the prefix and content and sets it as the bubble's HTML
bubble.elt.scrollIntoView({ behavior: 'smooth', block: 'end' });
Smoothly scrolls the new message into view so it's always visible to the user

📦 Key Variables

inputBox p5.js HTML element

Stores a reference to the text input field where users type messages; used to get the message value and clear it after sending

let inputBox = createInput();
sendButton p5.js HTML element

Stores the Send button; connected to the sendMessage function via mousePressed()

let sendButton = createButton("Send");
chatContainer p5.js HTML element

The scrollable div that holds all chat message bubbles; messages are added as children of this container

chatContainer = createDiv();
thinkingCowImageElement p5.js HTML element

Stores the thinking image element so it can be removed after the AI's delay; set to null when removed to free memory

thinkingCowImageElement = createImg(...);
bobQuestions array of strings

Array of conversational questions the AI can ask the user; a random one is picked 15% of the time

let bobQuestions = ["What's your favorite thing to do?", "Have you learned anything interesting lately?"];
lastQuestionAsked string

Stores the last question asked to avoid immediate repetition; the AI tries up to 10 times to pick a different one

let lastQuestionAsked = "";
keypressListener function

Stores the keyboard event listener function so it can be removed later when the dark screen easter egg triggers

keypressListener = function(e) { if (e.key === "Enter") sendMessage(); };
mainContainer p5.js HTML element

The outermost flex container that holds the title, chat area, and input field; used to hide everything when the dark screen easter egg triggers

mainContainer = createDiv();
inputArea p5.js HTML element

The flex container at the bottom that holds the input field and Send button side-by-side

inputArea = createDiv();
titleElement p5.js HTML element

The h1 heading that displays the chat app's title

titleElement = createElement('h1', 'POTATO AI 1.0🥔');

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG sendMessage()

If the user sends a message immediately after clicking Send, the thinking image might not exist when trying to remove it, causing a potential null reference error.

💡 Add an additional null check: if (thinkingCowImageElement && thinkingCowImageElement.elt) before calling .remove().

PERFORMANCE addMessage() with repeatingImage handler

Creating 10 image bubbles with recursive setTimeout calls and addMessage() recursion is inefficient and causes 10 separate DOM updates.

💡 Create a loop-based approach instead: move setTimeout outside the recursion and batch DOM updates, or use requestAnimationFrame() for smoother performance.

BUG addMessage() dark screen handler

The dark screen easter egg hides mainContainer but doesn't restore it if the page is refreshed or the exit button is clicked—the UI is permanently broken.

💡 Add a restore function that shows mainContainer, re-adds the keypress listener, and resets the background color. Trigger it when the exit button is clicked.

STYLE generateResponse()

The function is very long (~120 lines) with many conditionals, making it hard to read and maintain.

💡 Refactor into smaller helper functions like isGreeting(), isCodeRequest(), pickRandomQuestion() to reduce cognitive load and improve reusability.

FEATURE addMessage()

User and AI messages don't show timestamps, so it's hard to track conversation flow over time.

💡 Add a timestamp to each bubble: const time = new Date().toLocaleTimeString(); and display it in a small gray text above or below each message.

BUG setup() exit button

The exit button always opens the same Canva link regardless of user choice—it's not actually 'exiting' the app.

💡 Change the exit button to reload the page (window.location.reload()) or close the tab (window.close()), or make it configurable based on context.

🔄 Code Flow

Code flow showing setup, sendmessage, generateresponse, addmessage

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> main-container-flex[Main flex container] draw --> keypress-listener[Enter key listener] draw --> exit-button[Exit button with click handler] click setup href "#fn-setup" click draw href "#fn-draw" click main-container-flex href "#sub-main-container-flex" click keypress-listener href "#sub-keypress-listener" click exit-button href "#sub-exit-button" keypress-listener --> input-validation[Empty message guard] input-validation -->|Valid input| sendmessage[sendMessage] input-validation -->|Invalid input| draw click input-validation href "#sub-input-validation" sendmessage --> thinking-image[Thinking image display] sendmessage --> async-delay[Async response delay] sendmessage --> generateresponse[generateResponse] click sendmessage href "#fn-sendmessage" click thinking-image href "#sub-thinking-image" click async-delay href "#sub-async-delay" click generateresponse href "#fn-generateresponse" generateresponse --> easter-egg-check[Easter egg trigger] generateresponse --> greeting-check[Greeting detection] generateresponse --> priority-easter-egg[Priority easter egg] generateresponse --> repeating-image-trigger[Repeating image trigger] generateresponse --> keyword-triggers[Keyword detection] generateresponse --> code-refusal[Code request refusal] generateresponse --> reading-refusal[Reading/explaining refusal] generateresponse --> question-ask-logic[AI asks a question] generateresponse --> off-script-phrases[Off-script random response] click easter-egg-check href "#sub-easter-egg-check" click greeting-check href "#sub-greeting-check" click priority-easter-egg href "#sub-priority-easter-egg" click repeating-image-trigger href "#sub-repeating-image-trigger" click keyword-triggers href "#sub-keyword-triggers" click code-refusal href "#sub-code-refusal" click reading-refusal href "#sub-reading-refusal" click question-ask-logic href "#sub-question-ask-logic" click off-script-phrases href "#sub-off-script-phrases" question-ask-logic -->|Try up to 10 times| draw addmessage[addMessage] --> darkscreen-handler[Dark screen easter egg handler] addmessage --> repeating-image-handler[Repeating image handler] addmessage --> bubble-creation[Message bubble styling] click addmessage href "#fn-addmessage" click darkscreen-handler href "#sub-darkscreen-handler" click repeating-image-handler href "#sub-repeating-image-handler" click bubble-creation href "#sub-bubble-creation" bubble-creation --> sender-styling[Sender-specific styling] sender-styling --> content-type-check[Image vs text content] click sender-styling href "#sub-sender-styling" click content-type-check href "#sub-content-type-check"

❓ Frequently Asked Questions

What visual elements does the Potato AI sketch feature?

The sketch showcases a playful chat interface with a title element and a scrolling chat container, all designed with a lighthearted potato theme.

How can users engage with the Potato AI in this sketch?

Users can type their responses into an input box and hit send to participate in a quirky conversation with the Potato AI, which asks fun and random questions.

What creative coding concepts are demonstrated in the Potato AI sketch?

This sketch illustrates the use of interactive web elements, dynamic question generation, and playful UI design to create an engaging user experience.

Preview

potato ai official 1.0 (corbun does weird webs upd ) - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of potato ai official 1.0 (corbun does weird webs upd ) - Code flow showing setup, sendmessage, generateresponse, addmessage
Code Flow Diagram