<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikisha</title>
    <description>The latest articles on DEV Community by Nikisha (@nikisha_404).</description>
    <link>https://dev.to/nikisha_404</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166886%2F445dce96-2f04-4db7-8a92-2b66dd8a278b.jpeg</url>
      <title>DEV Community: Nikisha</title>
      <link>https://dev.to/nikisha_404</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikisha_404"/>
    <language>en</language>
    <item>
      <title>Dinner Wasn't Missing Ingredients. It Was Missing Coordination: Building New Dish with Local Open-Source AI</title>
      <dc:creator>Nikisha</dc:creator>
      <pubDate>Sun, 18 Oct 2026 21:17:14 +0000</pubDate>
      <link>https://dev.to/nikisha_404/dinner-wasnt-missing-ingredients-it-was-missing-coordination-building-new-dish-with-local-49kb</link>
      <guid>https://dev.to/nikisha_404/dinner-wasnt-missing-ingredients-it-was-missing-coordination-building-new-dish-with-local-49kb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The idea arrived during one of those completely ordinary dinner moments.&lt;/p&gt;

&lt;p&gt;I opened the fridge and found tomatoes, some cooked rice, half an onion, and several other ingredients that were all technically food but did not yet feel like dinner. A one of my friend had spinach and chickpeas and the other had carrots and yogurt.&lt;/p&gt;

&lt;p&gt;Individually, none of us had an exciting plan. Together, we almost had a meal.&lt;/p&gt;

&lt;p&gt;That thought became &lt;strong&gt;New Dish&lt;/strong&gt;: a local-first community kitchen that turns ingredients from one to three households into one real, safe recipe.&lt;/p&gt;

&lt;p&gt;It works for one person too. You can type what you have, photograph an ingredient list, or take a picture of your fridge. New Dish proposes ingredients, asks you to confirm them, applies the group's allergies and preferences, and matches everything against a documented recipe.&lt;/p&gt;

&lt;p&gt;The people I imagine paying for this are roommates, shared apartments, hostels, small community kitchens, and sustainability groups trying to reduce food waste. A paid version could add a shared weekly pantry, expiry reminders, shopping lists, and private household groups. The core version stays useful without an account or an API bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the idea changed
&lt;/h3&gt;

&lt;p&gt;My first version was called &lt;strong&gt;CrowdChef&lt;/strong&gt;. It imagined nearby households discovering one another and pooling leftovers.&lt;/p&gt;

&lt;p&gt;It sounded fun, but it was too large. Location, strangers, trust, scheduling, and food safety all arrived before anyone had cooked a single thing.&lt;/p&gt;

&lt;p&gt;So I reduced the idea to a small, real moment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One person should be able to use it.&lt;/li&gt;
&lt;li&gt;A few known neighbors or roommates can join.&lt;/li&gt;
&lt;li&gt;Everyone's allergies must be treated as group-wide boundaries.&lt;/li&gt;
&lt;li&gt;The result has to be an actual edible recipe, not an AI improvisation.&lt;/li&gt;
&lt;li&gt;Nutrition must come from a source, not from a model guessing confidently.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That smaller idea was much better. The cartoon interface and the animated pepper guide, &lt;strong&gt;Peper&lt;/strong&gt;, came later because dinner planning should feel friendly—not like filling out tax software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0qjgpgzr8pt6wmqiwag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs0qjgpgzr8pt6wmqiwag.png" alt="New Dish desktop interface" width="799" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full New Dish workspace: kitchens on the left, a verified recipe on the right.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🚀 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://nikisha77.github.io/New-Dish/#recipe" rel="noopener noreferrer"&gt;New Dish&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The quickest way to try it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose one, two, or three kitchens.&lt;/li&gt;
&lt;li&gt;Rename the neighbor kitchens if you want.&lt;/li&gt;
&lt;li&gt;Add ingredients manually or scan a photo.&lt;/li&gt;
&lt;li&gt;Select allergies and cooking preferences.&lt;/li&gt;
&lt;li&gt;Create the dish and review the source recipe, missing ingredients, instructions, and per-serving nutrition.&lt;/li&gt;
&lt;li&gt;Ask Peper to read the recipe aloud.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The app is responsive, so the same flow works on a phone:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4rgg246u91esaax7ng4j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4rgg246u91esaax7ng4j.png" alt="Mobile UI" width="800" height="1582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete project is open on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nikisha77" rel="noopener noreferrer"&gt;
        nikisha77
      &lt;/a&gt; / &lt;a href="https://github.com/nikisha77/New-Dish" rel="noopener noreferrer"&gt;
        New-Dish
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;New Dish&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;New Dish is a cheerful, local-first recipe matcher for one person or a group. Kitchens can type ingredients or confirm suggestions from an ingredient-list/fridge photo, choose group-wide dietary boundaries, and get a documented edible recipe with source nutrition per serving.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Local AI&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Verified recipes: UniTools Recipes v1 (CC BY-SA 4.0), including quantities, detailed method, timings, licensed photos, and source-provided calories/protein/fat/carbs per serving.&lt;/li&gt;
&lt;li&gt;OCR: Tesseract.js with local image preprocessing.&lt;/li&gt;
&lt;li&gt;Fridge suggestions: &lt;code&gt;Xenova/detr-resnet-50&lt;/code&gt; through Transformers.js. Detections always require confirmation.&lt;/li&gt;
&lt;li&gt;Optional wording helper: &lt;code&gt;HuggingFaceTB/SmolLM2-360M-Instruct&lt;/code&gt;, falling back to &lt;code&gt;onnx-community/Qwen2.5-0.5B-Instruct&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Voice: &lt;code&gt;onnx-community/Kokoro-82M-v1.0-ONNX&lt;/code&gt; through Kokoro.js.&lt;/li&gt;
&lt;li&gt;Models run in browser workers. Model files are downloaded once and cached by the browser.&lt;/li&gt;
&lt;li&gt;The language model never creates recipe facts or nutrition. If no safe source recipe matches, the app says so instead of fabricating one.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run locally&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python3 -m http.server 4173 --directory dist&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then open &lt;a href="http://127.0.0.1:4173" rel="nofollow noopener noreferrer"&gt;http://127.0.0.1:4173&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;or try here - &lt;a href="https://nikisha77.github.io/New-Dish/#recipe" rel="nofollow noopener noreferrer"&gt;https://nikisha77.github.io/New-Dish/#recipe&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nikisha77/New-Dish" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Repository: &lt;a href="https://github.com/nikisha77/New-Dish" rel="noopener noreferrer"&gt;github.com/nikisha77/New-Dish&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no backend and no secret API key. The application is static HTML, CSS, JavaScript, Web Workers, open models, and an open recipe dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The architecture
&lt;/h3&gt;

&lt;p&gt;New Dish deliberately separates &lt;strong&gt;recognition&lt;/strong&gt;, &lt;strong&gt;recipe truth&lt;/strong&gt;, and &lt;strong&gt;presentation&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fridge photo / ingredient label
              ↓
   Local OCR + object detection
              ↓
      Human confirmation
              ↓
 Allergies + dietary preferences
              ↓
   Verified open recipe matching
              ↓
 Recipe, licensed photo, instructions
 and sourced nutrition per serving
              ↓
       Peper's local voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Reading a fridge without uploading it
&lt;/h3&gt;

&lt;p&gt;For printed ingredient lists, I use &lt;strong&gt;Tesseract.js&lt;/strong&gt; in the browser. Before OCR, the image is resized and processed using grayscale, contrast enhancement, and a second high-contrast pass when the first result is weak.&lt;/p&gt;

&lt;p&gt;For fridge photographs, a &lt;strong&gt;DETR ResNet-50&lt;/strong&gt; object-detection model runs through Hugging Face Transformers.js in a Web Worker. The model never receives an uploaded URL from a server; it works with the image selected in the browser.&lt;/p&gt;

&lt;p&gt;The most important part is what happens after inference: &lt;strong&gt;the app does not automatically trust it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My first fridge test was humbling. The recognizer found one correct word—&lt;code&gt;apple&lt;/code&gt;—surrounded by a parade of meaningless fragments. The pipeline had technically succeeded, but the result was useless.&lt;/p&gt;

&lt;p&gt;I fixed that by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;matching OCR output against a vocabulary built from the recipe dataset;&lt;/li&gt;
&lt;li&gt;rejecting random text fragments;&lt;/li&gt;
&lt;li&gt;deduplicating singular and plural ingredient names;&lt;/li&gt;
&lt;li&gt;requiring higher confidence for fridge detections;&lt;/li&gt;
&lt;li&gt;preselecting only reliable suggestions; and&lt;/li&gt;
&lt;li&gt;requiring the person using the app to confirm every ingredient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That failure changed the product. A confident model is not the same thing as a correct model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Refusing to let a language model invent dinner
&lt;/h3&gt;

&lt;p&gt;The early prototype asked a small language model to create a recipe. That was exciting until I asked a more important question: &lt;em&gt;Would I actually cook and serve this?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A plausible sentence is not evidence that a recipe exists. The same problem applies to nutrition. A model can produce wonderfully formatted calories and macros that have no relationship to the actual meal.&lt;/p&gt;

&lt;p&gt;So the final architecture uses &lt;strong&gt;UniTools Recipes v1&lt;/strong&gt;, an open CC BY-SA 4.0 collection of documented recipes. Each record includes ingredient quantities, scaling behavior, detailed steps, timings, a licensed image, and source-provided calories, protein, carbohydrates, and fat per serving.&lt;/p&gt;

&lt;p&gt;The browser matches confirmed pantry ingredients against that data, removes recipes conflicting with selected allergies, applies preferences such as vegan, vegetarian, gluten-free, dairy-free, high-protein, easy, and cooking-time limits, then scales the ingredient quantities for the requested number of people.&lt;/p&gt;

&lt;p&gt;If no safe recipe matches, New Dish says so. It does not manufacture one to avoid an empty screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Open models with graceful fallbacks
&lt;/h3&gt;

&lt;p&gt;The optional local language helper uses &lt;strong&gt;SmolLM2 360M Instruct&lt;/strong&gt; as its primary open-weight model and &lt;strong&gt;Qwen2.5 0.5B Instruct&lt;/strong&gt; as the fallback. Both run through Transformers.js, using WebGPU when available and WASM otherwise.&lt;/p&gt;

&lt;p&gt;They are intentionally not responsible for recipe facts or nutrition. Their role is limited to presentation help, where a wording error is inconvenient rather than unsafe.&lt;/p&gt;

&lt;p&gt;Peper's natural voice uses the open &lt;strong&gt;Kokoro 82M&lt;/strong&gt; model locally. If that model cannot load, the app falls back to the browser's device voice. Peper's face animates while speaking, which is a small detail but makes the interface feel alive.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Safety and privacy in the interface
&lt;/h3&gt;

&lt;p&gt;Allergy exclusions apply to the complete group. The current list includes peanuts, tree nuts, dairy, eggs, gluten, soy, fish, shellfish, sesame, mustard, celery, lupin, and sulphites.&lt;/p&gt;

&lt;p&gt;Typed ingredients that match a selected allergen are removed from recipe matching, and recipe records are checked too. The interface still reminds people to verify packaging and cross-contact because software cannot inspect a kitchen.&lt;/p&gt;

&lt;p&gt;Neighbor names are chosen by the user and saved only in browser storage. Images and ingredients stay on the device. No account is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Shipping it
&lt;/h3&gt;

&lt;p&gt;New Dish is a buildless static application hosted on &lt;strong&gt;Github pages&lt;/strong&gt;. That keeps deployment simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open innovation is not decoration in this project. It changes whether the idea is practical.&lt;/p&gt;

&lt;p&gt;With closed APIs, every photo scan, recipe request, and spoken instruction could become a metered request. That would make a small household tool expensive before it had its first paying customer. It would also require sending photographs of people's kitchens and food habits to servers they did not choose.&lt;/p&gt;

&lt;p&gt;Using open models locally gave me four important freedoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; fridge photographs and ingredient lists stay on the device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low operating cost:&lt;/strong&gt; using the app does not create a growing inference bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replaceability:&lt;/strong&gt; SmolLM2, Qwen, DETR, Tesseract, and Kokoro can be updated or exchanged without redesigning the product around one vendor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty:&lt;/strong&gt; model output can be constrained to what models do well, while recipe and nutrition facts come from inspectable open data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most valuable freedom is the ability to disagree with the model. I can put confidence thresholds around it, add a food vocabulary, require human confirmation, or remove it entirely from a safety-sensitive step. A closed black-box response would give me fewer places to intervene.&lt;/p&gt;

&lt;p&gt;Open did not simply make New Dish cheaper. It made the product's privacy and trust model possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;My first user was me, standing in front of a fridge and expecting the camera feature to understand what I could already see.&lt;/p&gt;

&lt;p&gt;It did not work beautifully on the first attempt. It returned one correct apple and a lot of nonsense. That was actually the most useful feedback I could have received because it exposed a dangerous assumption: I was treating “the model returned something” as “the model understood the image.”&lt;/p&gt;

&lt;p&gt;After adding food-vocabulary validation and confidence thresholds, I tested the exact failed output again. Only &lt;code&gt;apple&lt;/code&gt; survived.&lt;/p&gt;

&lt;p&gt;No one has paid for New Dish yet. I would rather say that honestly than invent traction for a submission. But the product already received its first useful requests: support a single person, show verified nutrition per serving, let users set their neighbors' names, and never fabricate a recipe. Each request changed the app.&lt;/p&gt;

&lt;p&gt;The next real test is putting it in front of roommates or a shared household for a week and measuring whether it helps them rescue at least one dinner they would otherwise have abandoned.&lt;/p&gt;




&lt;h3&gt;
  
  
  Open-source building blocks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/docs/transformers.js/" rel="noopener noreferrer"&gt;Transformers.js&lt;/a&gt; for in-browser model inference&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct" rel="noopener noreferrer"&gt;SmolLM2-360M-Instruct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/onnx-community/Qwen2.5-0.5B-Instruct" rel="noopener noreferrer"&gt;Qwen2.5-0.5B-Instruct ONNX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/Xenova/detr-resnet-50" rel="noopener noreferrer"&gt;DETR ResNet-50 for Transformers.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/naptha/tesseract.js" rel="noopener noreferrer"&gt;Tesseract.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX" rel="noopener noreferrer"&gt;Kokoro 82M ONNX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;UniTools Recipes v1, CC BY-SA 4.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading. If your fridge has ever contained plenty of ingredients but somehow no dinner, New Dish is for you.&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>opensource</category>
      <category>hf26challenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>The Next Chapter Is Outside: Building an Open-Source AI Storybook with Streamlit</title>
      <dc:creator>Nikisha</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:09:12 +0000</pubDate>
      <link>https://dev.to/nikisha_404/the-next-chapter-is-outside-building-an-open-source-ai-storybook-with-streamlit-2g8i</link>
      <guid>https://dev.to/nikisha_404/the-next-chapter-is-outside-building-an-open-source-ai-storybook-with-streamlit-2g8i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The next chapter is outside
&lt;/h1&gt;

&lt;p&gt;Most digital stories ask us to sit still.&lt;/p&gt;

&lt;p&gt;We read, tap “next,” watch another animation, and stay exactly where we are. When I saw the &lt;strong&gt;Touch Grass&lt;/strong&gt; prompt, I wanted to reverse that relationship. What if a story could begin on a screen, send you outdoors, and refuse to continue until you came back with something you had discovered?&lt;/p&gt;

&lt;p&gt;That idea became &lt;strong&gt;Outside the Lines&lt;/strong&gt;: a Python and Streamlit story experience in which the player chooses a comic adventure, reads one short chapter, receives a real-world mission, and photographs something they find outside. An open-weight vision model interprets the discovery, and the object becomes part of the next chapter.&lt;/p&gt;

&lt;p&gt;The screen is not the destination. It is the doorway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwl4pyhled488vmed1ynh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwl4pyhled488vmed1ynh.png" alt="The three Outside the Lines adventures" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Outside the Lines is an interactive outdoor storybook for children, families, and anyone who needs a playful excuse to step away from a screen.&lt;/p&gt;

&lt;p&gt;The player chooses one of three stories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Barnaby’s Great Bush Disaster&lt;/strong&gt; — an overconfident orange cat attempts an heroic squirrel chase and discovers gravity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leo and the Lunch-Lover&lt;/strong&gt; — a peaceful mountain picnic becomes a very fast argument with a hungry goat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maya’s Flying Masterpiece&lt;/strong&gt; — a gust of wind turns an artist’s canvas into a kite and the hillside into a rescue mission.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each story has three chapters and three outdoor missions. A mission might ask the player to find a leaf that looks brave, a natural shape that points toward an escape route, or a color that belongs in Maya’s recovered painting.&lt;/p&gt;

&lt;p&gt;The interaction loop is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choose a story
      ↓
Watch and listen to a short chapter
      ↓
Receive a safe outdoor mission
      ↓
Step away from the screen
      ↓
Photograph a natural object
      ↓
OpenCLIP suggests what it might be
      ↓
The player confirms or changes the answer
      ↓
That discovery appears in the next chapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The most important product decision was that the AI does not replace the player’s imagination. It gives a suggestion, but the player can edit it. If the model thinks a seed pod is a stone, the child does not have to accept the machine’s answer. The discovery still belongs to the person who found it.&lt;/p&gt;

&lt;p&gt;The missions also avoid collecting, damaging, or feeding wildlife. The player can photograph an object where it is and leave it outside.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try the Streamlit app here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://outside-the-lines.streamlit.app/" rel="noopener noreferrer"&gt;Launch Outside the Lines&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first use of the vision or narration features can take longer because the open model weights need to be downloaded and cached. The story itself remains playable while those optional features load.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deployment note: the Streamlit Community Cloud app must be set to public before submitting, otherwise visitors will see a sign-in page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The complete Python project is open source:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nikisha77" rel="noopener noreferrer"&gt;
        nikisha77
      &lt;/a&gt; / &lt;a href="https://github.com/nikisha77/outside-the-lines" rel="noopener noreferrer"&gt;
        outside-the-lines
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Outside the Lines&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A Streamlit adventure that turns three real-world discoveries into a moving
hand-drawn story. The application stack is Python from end to end.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Open models&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenCLIP ViT-B/32&lt;/strong&gt; interprets trail photos in Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kokoro-82M&lt;/strong&gt; performs the narration with a natural voice in Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pillow&lt;/strong&gt; creates chapter-specific animated WebP scenes in Python.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No Gemini, proprietary model API, JavaScript application, Node build, account,
or location tracker is required. Model weights are fetched once and cached.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; outside-the-lines
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/streamlit run app.py&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:8501" rel="nofollow noopener noreferrer"&gt;http://localhost:8501&lt;/a&gt;. The story itself works immediately. OpenCLIP and
Kokoro download their model weights the first time their features are used.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Structure&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.py&lt;/code&gt; — Streamlit screens, navigation, session state, and UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;story_data.py&lt;/code&gt; — all three stories and their outdoor missions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai_services.py&lt;/code&gt; — animation, photo understanding, and narration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assets/&lt;/code&gt; — the supplied comic reference art and its story panels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requirements.txt&lt;/code&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nikisha77/outside-the-lines" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Repository: &lt;a href="https://github.com/nikisha77/outside-the-lines" rel="noopener noreferrer"&gt;github.com/nikisha77/outside-the-lines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is deliberately small enough to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outside-the-lines/
├── app.py              # Streamlit UI, navigation, and session state
├── story_data.py       # Three stories and nine outdoor missions
├── ai_services.py      # Animation, vision, and narration
├── requirements.txt    # Reproducible Python dependencies
└── assets/             # Comic artwork and story panels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I started with the story loop, not the model
&lt;/h3&gt;

&lt;p&gt;The first version was a static web story. It looked polished at a glance, but it had the wrong kind of motion: leaves floated, objects pulsed, and the page moved without the characters actually &lt;em&gt;doing&lt;/em&gt; anything.&lt;/p&gt;

&lt;p&gt;That exposed an important difference. An animated interface is not the same thing as an animated scene.&lt;/p&gt;

&lt;p&gt;I rewrote the experience around character actions: Barnaby crouches, launches, and crashes; Leo and Billy race around the boulder; Maya chases a canvas down the hill. The movement needed to support the comedy and make each chapter feel like a moment in a comic, not a collection of decorative effects.&lt;/p&gt;

&lt;p&gt;I also expanded the stories. Instead of a single shallow scene, each theme now has anticipation, action, impact, and an ending affected by what the player finds outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I moved the complete application to Python and Streamlit
&lt;/h3&gt;

&lt;p&gt;I wanted one readable stack for the interface, story state, model inference, audio, and animation. Streamlit made that possible without maintaining a separate JavaScript application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app.py&lt;/code&gt; controls the screens and stores the current story, chapter, discoveries, and audio state in Streamlit session state. &lt;code&gt;story_data.py&lt;/code&gt; keeps the narrative separate from the UI, so a story can be edited, translated, or replaced without changing the application logic.&lt;/p&gt;

&lt;p&gt;Expensive results are cached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@st.cache_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;show_spinner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scene_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;animated_scene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because model-backed interactions feel much better when rerunning the Streamlit script does not regenerate the same media every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. OpenCLIP turns a trail photo into a story ingredient
&lt;/h3&gt;

&lt;p&gt;For image understanding, I used &lt;strong&gt;OpenCLIP ViT-B/32&lt;/strong&gt; with LAION pretrained weights.&lt;/p&gt;

&lt;p&gt;A mission supplies a small set of relevant labels such as “a leaf,” “a twig,” “a stone,” “tree bark,” or “a seed pod.” OpenCLIP embeds the uploaded image and each candidate label into the same feature space. The app then chooses the label with the highest similarity.&lt;/p&gt;

&lt;p&gt;The core is compact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preprocess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open_clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_model_and_transforms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ViT-B-32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pretrained&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;laion2b_s34b_b79k&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open_clip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ViT-B-32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text_features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image_features&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;image_features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text_features&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;text_features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keepdim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;image_features&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;text_features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;softmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;best_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;())]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose constrained candidate labels on purpose. This is not a general “tell me everything in this image” chatbot. It is a small, understandable classification step tied to the current mission. That makes it faster, easier to explain, and easier for the player to correct.&lt;/p&gt;

&lt;p&gt;If the model cannot load, the app falls back to a neutral “outdoor discovery” instead of breaking the story. AI should enhance the adventure, not hold it hostage.&lt;/p&gt;

&lt;p&gt;In the hosted version, the uploaded photo is processed by the Streamlit server. It is not sent to a separate proprietary vision API, and the app does not request location data. For the strongest privacy boundary, the same repository can be run locally so inference stays under the player’s control.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Kokoro gives the stories a warmer voice
&lt;/h3&gt;

&lt;p&gt;The first narration attempt sounded like a utility reading a notification. That did not fit a mischievous cat, a terrified hiker, or a painter sprinting downhill.&lt;/p&gt;

&lt;p&gt;I replaced it with &lt;strong&gt;Kokoro-82M&lt;/strong&gt;, an open-weight text-to-speech model. Narration is produced as a WAV file and cached using a hash of the voice and story text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lang_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;audio&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;af_heart&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;audio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concatenate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WAV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The slightly slower pace makes the voice feel more like story narration than a screen reader. Because the voice layer is independent, another Kokoro voice—or another open TTS model—can be substituted without rewriting the story system.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Pillow creates motion without a video-generation bill
&lt;/h3&gt;

&lt;p&gt;The moving scenes are generated in Python with Pillow and NumPy. Each chapter uses a different camera rhythm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;anticipation uses a gentle push-in,&lt;/li&gt;
&lt;li&gt;chase scenes pan and add motion arcs,&lt;/li&gt;
&lt;li&gt;impacts use a quick zoom, shake, and comic burst.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Twenty frames are rendered into a looping animated WebP. That is not full character animation, and I do not pretend it is. It is a lightweight comic-motion system that runs predictably, costs nothing per scene, and works with the supplied hand-drawn panels.&lt;/p&gt;

&lt;p&gt;The architecture also leaves a clean seam for future work: generated or hand-animated frames can replace the source panel without changing the Streamlit story flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Deployment taught me to respect the Python version
&lt;/h3&gt;

&lt;p&gt;The app worked locally, but the first Streamlit deployment failed while building &lt;code&gt;blis&lt;/code&gt;, a dependency pulled in by spaCy. The cloud environment had selected Python 3.14, while the dependency chain expected a version with compatible wheels.&lt;/p&gt;

&lt;p&gt;The fix was not an elaborate code change. I redeployed with Python 3.11 and kept the dependencies explicit in &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That debugging moment was a good reminder: an open-source project is only useful when another machine can actually install it. The model choice gets attention, but reproducible environments are part of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open models are not decoration in this project. They are the parts that make the interaction possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The project does not pay for every discovery.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
There is no per-photo vision request and no per-sentence narration bill. That matters for a playful app that may be used repeatedly by a family, classroom, or community group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The models can be inspected and replaced.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
OpenCLIP and Kokoro sit behind small Python functions. A developer can swap the vision backbone, change the label set, choose a different voice, or run quantized models on smaller hardware. The application is not designed around one company’s response format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The local path is real.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The public demo runs on Streamlit Community Cloud for convenience, but the same code can run on a laptop without sending photos to a third-party AI API. Once weights are downloaded and cached, the model layer can be controlled by the person or organization running the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure does not end the story.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Open model weights can be large, networks can disappear, and low-memory machines exist. The narrative and missions continue even if vision or narration is unavailable. The human activity remains the primary feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The player stays in charge of meaning.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A model prediction is shown as an editable suggestion. This is especially important for a children’s experience: the software can notice patterns, but it should not teach the player that an AI label is unquestionable truth.&lt;/p&gt;

&lt;p&gt;A closed multimodal API could probably produce a more fluent description with less setup. But it would also introduce a remote dependency, unpredictable usage cost, less control over model behavior, and a harder privacy conversation around children’s outdoor photographs. For this project, the flexibility of open components is a better fit than maximum model size.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The hardest part was not connecting a model. It was deciding where AI belonged.&lt;/p&gt;

&lt;p&gt;My first instinct was to add more generation: more animation, more description, more automatic storytelling. The better version uses AI in two narrow places—recognizing a discovery and narrating a chapter—while the player supplies the movement, attention, and imagination.&lt;/p&gt;

&lt;p&gt;I also learned that “get people outside” cannot be solved with outdoor-colored pixels. The product needs a reason to close the laptop or put down the phone. In Outside the Lines, progress pauses until the player looks at the real world.&lt;/p&gt;

&lt;p&gt;There are still limitations. OpenCLIP chooses from a constrained list and can be wrong. Kokoro’s first load can be slow. The animated WebP scenes are expressive camera motion, not fully articulated character animation. Streamlit Community Cloud also means the hosted version is not an offline trail app.&lt;/p&gt;

&lt;p&gt;Those are useful boundaries, not hidden defects. The next steps are to add a downloadable offline mode, more accessible mission alternatives, multilingual stories, and community-authored story packs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I care about most
&lt;/h2&gt;

&lt;p&gt;I do not want Outside the Lines to become another app that rewards a child for staring at it longer.&lt;/p&gt;

&lt;p&gt;The ideal session is a few minutes of story, a much longer stretch of noticing leaves, stones, shadows, wind, bark, and paths, and then a quick return to see how that discovery changed the adventure.&lt;/p&gt;

&lt;p&gt;If the player remembers the squirrel, the goat, or the flying canvas, that is lovely.&lt;/p&gt;

&lt;p&gt;If they remember the strange leaf they found outside, that is the real ending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I am entering the &lt;strong&gt;overall Hacktoberfest Open-Source AI Challenge: Week 1 — Touch Grass&lt;/strong&gt; category. The current build does not claim a partner-technology prize category.&lt;/p&gt;

&lt;p&gt;Thanks for reading—and please close this tab for a minute when you are done. The next chapter is outside.&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>python</category>
    </item>
  </channel>
</rss>
