<?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: πn</title>
    <description>The latest articles on DEV Community by πn (@ptbdtq).</description>
    <link>https://dev.to/ptbdtq</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%2F1408910%2F0a00c928-2f60-4e4a-88c5-007023b2ac0f.jpg</url>
      <title>DEV Community: πn</title>
      <link>https://dev.to/ptbdtq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ptbdtq"/>
    <language>en</language>
    <item>
      <title>GHOSTLIGHT: Stadium of Echoes Cover</title>
      <dc:creator>πn</dc:creator>
      <pubDate>Sun, 01 Nov 2026 06:56:40 +0000</pubDate>
      <link>https://dev.to/ptbdtq/ghostlight-stadium-of-echoes-cover-ek2</link>
      <guid>https://dev.to/ptbdtq/ghostlight-stadium-of-echoes-cover-ek2</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Football Remembers the Scores. We Remember the People.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;🏟️ &lt;strong&gt;Live Showcase:&lt;/strong&gt; &lt;a href="https://ghostlight-stadium-of-echoes.onrender.com" rel="noopener noreferrer"&gt;https://ghostlight-stadium-of-echoes.onrender.com&lt;/a&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  📦 &lt;strong&gt;Source Code (GitHub):&lt;/strong&gt; &lt;a href="https://github.com/budulay/ghostlight-stadium-of-echoes" rel="noopener noreferrer"&gt;ghostlight-stadium-of-echoes&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A football match lasts 90 minutes.&lt;br&gt;&lt;br&gt;
Sometimes 120.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;But a memory can last a lifetime.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For some people, it's the roar of thousands of supporters. For others, it's the smell of wet grass, the glow of floodlights through the rain, an old ticket kept in a drawer, or the feeling of holding someone's hand while watching a match for the very first time.&lt;/p&gt;

&lt;p&gt;Years pass. Stadiums are renovated or torn down. Tickets fade. Results become statistics in databases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But the memories remain.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Football remembers the scores. GHOSTLIGHT remembers the people.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the idea behind &lt;strong&gt;GHOSTLIGHT: Stadium of Echoes&lt;/strong&gt; — a cinematic, interactive digital sanctuary where forgotten football memories come back to life through locally running open-weight AI.&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%2Fapx68co9q8676y8dfgfl.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%2Fapx68co9q8676y8dfgfl.png" alt="Hero — Forgotten Stadium" width="800" height="614"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Hero: The Forgotten Stadium under midnight floodlights.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea: A Stadium Full of Memories
&lt;/h2&gt;

&lt;p&gt;The theme of the &lt;strong&gt;Hacktoberfest 2026 Ghost in the Machine&lt;/strong&gt; challenge inspired me to explore a different kind of digital ghost.&lt;/p&gt;

&lt;p&gt;Not a ghost that scares people.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;A ghost that preserves something worth remembering.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted to build an experience that connects football heritage, personal storytelling, open-weight artificial intelligence, and digital preservation.&lt;/p&gt;

&lt;p&gt;The result is an interactive night stadium.&lt;/p&gt;

&lt;p&gt;At first, it is almost completely dark. Its stands are silent. Only a few distant floodlights cut through the midnight mist.&lt;/p&gt;

&lt;p&gt;Then someone leaves a memory.&lt;/p&gt;

&lt;p&gt;An old matchday photograph. A ticket stub. A scarf. A few sentences about a football match experienced decades ago.&lt;/p&gt;

&lt;p&gt;The AI helps transform that fragment into a living digital exhibit.&lt;br&gt;&lt;br&gt;
A section of the grandstand illuminates.&lt;br&gt;&lt;br&gt;
And the memory finds its eternal seat.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;GHOSTLIGHT is designed around a three-step emotional loop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leave an artifact → Awaken a memory → Explore its echoes.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The Forgotten Stadium
&lt;/h3&gt;

&lt;p&gt;The experience begins in an atmospheric football cathedral at night. The interface uses a midnight palette of near-black, deep navy, and warm luminous gold.&lt;/p&gt;

&lt;p&gt;Volumetric floodlights, animated fog, drifting particles, and editorial typography create the feeling of entering a place suspended between the past and present.&lt;/p&gt;

&lt;p&gt;The main invitation is simple:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;SOME MEMORIES NEVER LEAVE.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Users can awaken a new memory or explore the existing archive.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Leave an Artifact
&lt;/h3&gt;

&lt;p&gt;Users can awaken a memory by entering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A story or personal recollection&lt;/strong&gt;: A heartfelt paragraph of what they experienced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An approximate year&lt;/strong&gt;: e.g., 1968, 1982, or 1998.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A football club or team&lt;/strong&gt;: The colors they cheered for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A stadium&lt;/strong&gt;, if known: The cathedral where it occurred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An optional photograph or visual artifact&lt;/strong&gt;: Uploaded via drag-and-drop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uploaded artifact becomes the centerpiece of the exhibit.&lt;/p&gt;

&lt;p&gt;The system does not require users to know every historical detail. &lt;strong&gt;An incomplete memory is still deeply worth preserving.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. The Ghost Awakens — Gemma Meets Ollama
&lt;/h3&gt;

&lt;p&gt;This is where open-weight AI becomes the heartbeat of the experience.&lt;/p&gt;

&lt;p&gt;GHOSTLIGHT uses &lt;strong&gt;Google Gemma 3 4B&lt;/strong&gt;, running 100% locally through &lt;strong&gt;Ollama&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of sending personal family stories to a remote commercial cloud, the client communicates with a local Express backend, which prompts the local model with strict archival guardrails.&lt;/p&gt;

&lt;p&gt;The model transforms the submitted recollection into structured exhibit data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: An uppercase poetic exhibit name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approximate year&lt;/strong&gt;: Historical era anchoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme and emotional character&lt;/strong&gt;: e.g., &lt;em&gt;"Ancestral Reverence"&lt;/em&gt;, &lt;em&gt;"First Floodlight Glow"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrative&lt;/strong&gt;: An evocative, respectful story capturing the sensory atmosphere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closing inscription&lt;/strong&gt;: A stone-carved epitaph for the grandstand plaque.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt;: Connecting historical motifs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grounded recollections&lt;/strong&gt;: Confirmed facts stated by the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown or missing details&lt;/strong&gt;: Respectfully cataloged as &lt;em&gt;"Lost to the stadium winds"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  The Ethical AI Principle: Enrich, Never Rewrite History
&lt;/h3&gt;

&lt;p&gt;The system strictly distinguishes information explicitly provided by the user from details that remain unknown.&lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;never invents&lt;/strong&gt; match results, fictional player lineups, attendance figures, or synthetic deceased voices simply to make a story sound complete.&lt;/p&gt;

&lt;p&gt;A missing scoreline is not a bug to fix. It is part of what makes a human memory authentic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. The Memory Reveal
&lt;/h3&gt;

&lt;p&gt;Once the memory has been processed during the floodlight power-up séance, the stadium begins to awaken:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A newly allocated seat in the grandstand lights up.&lt;/li&gt;
&lt;li&gt;The exhibit title emerges in gold typography alongside the original uploaded artifact.&lt;/li&gt;
&lt;li&gt;Two distinct columns tell the story:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounded Recollections&lt;/strong&gt;: Details verified by the person who submitted the memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost to the Winds&lt;/strong&gt;: Information that remains forgotten in time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The closing inscription might read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Some matches never end.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. A Stadium That Speaks
&lt;/h3&gt;

&lt;p&gt;I wanted the experience to engage the ears as deeply as the eyes.&lt;/p&gt;

&lt;p&gt;GHOSTLIGHT includes two narration styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tannoy&lt;/strong&gt;: An authentic stadium announcer voice, preceded by a synthesized two-tone stadium bell chime (&lt;code&gt;D5 587Hz -&amp;gt; A4 440Hz&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whisper&lt;/strong&gt;: A quieter, intimate museum-style narration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser's native &lt;strong&gt;SpeechSynthesis API&lt;/strong&gt; provides the voice. Meanwhile, a procedural &lt;strong&gt;Web Audio API soundscape&lt;/strong&gt; adds a subtle stadium night hum, while the &lt;em&gt;Rain Derby&lt;/em&gt; weather mode introduces synthesized rainfall and a stormy visual atmosphere.&lt;/p&gt;

&lt;p&gt;Listening to a memory feels like attending a quiet private ceremony under the lights.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. The Vintage Matchday Ticket
&lt;/h3&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%2Fkyb8nni7tz67kvdmz00t.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%2Fkyb8nni7tz67kvdmz00t.png" alt=" " width="800" height="347"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Vintage Matchday Ticket: A physical-digital keepsake generated dynamically on HTML5 Canvas.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the most distinctive details is the &lt;strong&gt;Vintage Matchday Ticket generator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every memory can become a high-resolution (1200x520px) digital keepsake:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentic perforated edges with cutout notches.&lt;/li&gt;
&lt;li&gt;Archival exhibition title.&lt;/li&gt;
&lt;li&gt;Stand, sector, and seat coordinates.&lt;/li&gt;
&lt;li&gt;Period-inspired ink stamps (&lt;code&gt;PRESS ARCHIVE • AUTHENTICATED&lt;/code&gt; or &lt;code&gt;ALL HALLOWS' SPECIAL&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Procedural retro turnstile barcodes and serial numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can toggle between the &lt;strong&gt;Museum Exhibit Plaque&lt;/strong&gt; and the &lt;strong&gt;Vintage Matchday Ticket&lt;/strong&gt;, exporting either as a print-ready PNG image. It turns fleeting digital text into a tangible souvenir worth keeping.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. The Living Stadium and Echo Threads
&lt;/h3&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%2Fcbtu20lupge6pky4kzrh.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%2Fcbtu20lupge6pky4kzrh.png" alt=" " width="800" height="612"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Echo Threads: Luminous Bézier arcs connecting strangers through shared history.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The stadium is more than a backdrop — it is a living entity.&lt;/p&gt;

&lt;p&gt;Every saved memory occupies an illuminated coordinate in the stands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;North Stand&lt;/strong&gt;: Family devotions and childhood memories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;South Stand (Kop)&lt;/strong&gt;: Passionate chanting and terrace anthems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;West Grandstand&lt;/strong&gt;: Press boxes and tactical heritage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;East Terrace&lt;/strong&gt;: Historic standing terraces in the rain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Echo Threads&lt;/strong&gt; feature takes this further: it automatically calculates and draws glowing Bézier curves between memories that share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same decade (e.g., the 1970s or 1990s).&lt;/li&gt;
&lt;li&gt;The same football club.&lt;/li&gt;
&lt;li&gt;A common emotional resonance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Someone who remembers a rainy cup match in 1989 is visually connected to someone else who cheered for the same club in that era. The stadium becomes an illuminated web of collective human belonging.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. A Museum That Belongs to Its Owner
&lt;/h3&gt;

&lt;p&gt;Digital preservation should never mean handing ownership of personal memories over to an ad-driven cloud platform.&lt;/p&gt;

&lt;p&gt;GHOSTLIGHT follows an uncompromising &lt;strong&gt;local-first approach&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All memories and high-res artifact images are stored directly in your browser's &lt;strong&gt;IndexedDB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The archive provides full-text search, decade filters, card downloads, and JSON metadata exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Time Capsule&lt;/strong&gt;: Users can export their entire stadium collection into a self-contained &lt;code&gt;.ghostlight.json&lt;/code&gt; file, and import it onto any other computer or browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The museum belongs entirely to the person who created it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technology Behind the Experience
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 19, TypeScript, Vite 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Styling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4 (with zero-CLS theme variables)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Animation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framer Motion (spatial canvas transitions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend Proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js, Express 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Google Gemma 3 4B (Instruct)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Persistence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IndexedDB via &lt;code&gt;idb&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Narration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native Browser &lt;code&gt;SpeechSynthesis&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Acoustic Soundscape&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Web Audio API (realtime Tannoy chime + weather synthesis)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graphics &amp;amp; Artifacts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTML5 Canvas 2D image renderer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Architectural Flow:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  USER
    │
    ▼
  REACT 19 + VITE CLIENT
    │
    ├───► INDEXEDDB (Local Private Storage)
    │
    ▼
  EXPRESS 5 API (Port 3001)
    │
    ▼
  LOCAL OLLAMA ENGINE (Port 11434)
    │
    ▼
  GOOGLE GEMMA 3 4B (Strict JSON Mode)
    │
    ▼
  STRUCTURED MEMORY EXHIBIT
    │
    ▼
  MEMORY REVEAL (Seat Coordinates + Tannoy Audio + Vintage Ticket PNG)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core AI workflow does not require a cloud LLM subscription or an API key. Everything runs on the hardware right in front of you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;Running an open-weight model locally is not merely an engineering preference for GHOSTLIGHT. &lt;strong&gt;It is a core product principle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Personal photographs, deceased relatives' tickets, and intimate family memories are sacred. They deserve far better protection than being sent to third-party cloud servers for training or surveillance.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;Gemma 3 through Ollama&lt;/strong&gt; gives GHOSTLIGHT five vital guarantees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy &amp;amp; Control&lt;/strong&gt;: Memory text is processed on the user's machine without ever touching an external API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independence&lt;/strong&gt;: The application will work twenty years from now, even if commercial AI APIs vanish or change their pricing models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Anyone with consumer hardware can run the model locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership&lt;/strong&gt;: The archive and its &lt;code&gt;.ghostlight.json&lt;/code&gt; Time Capsule remain strictly under the user's control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility&lt;/strong&gt;: Any developer can clone the repository, pull &lt;code&gt;gemma3:4b&lt;/code&gt;, and immediately experience the full installation.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;One of the biggest lessons from this project is that &lt;strong&gt;AI applications do not always need to revolve around a conversational chatbot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes the most profound interface for an AI system is a &lt;strong&gt;spatial experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Gemma does not need to act like a sports statistician or guess match scores. Its role is far more human: helping people structure raw emotions, giving them an archival form, and preserving the sensory essence of a moment.&lt;/p&gt;

&lt;p&gt;The small details are what bring the soul to life:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The two-tone Tannoy chime before a story is spoken.&lt;/li&gt;
&lt;li&gt;The perforated tear-off line on a vintage matchday ticket.&lt;/li&gt;
&lt;li&gt;A single golden seat igniting in a darkened grandstand.&lt;/li&gt;
&lt;li&gt;A glowing thread connecting two strangers across fifty years of football history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These details transform GHOSTLIGHT from another AI demo into an authentic &lt;strong&gt;place of reverence&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;GHOSTLIGHT began with football, but the underlying philosophy can expand into many cultural realms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supporter Club Heritage Archives&lt;/strong&gt;: Preserving historic community memory before legendary stadiums are renovated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Family Legacy Museums&lt;/strong&gt;: Honoring passed generations and their shared passions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical Places &amp;amp; Vanished Landmarks&lt;/strong&gt;: Reviving theaters, concert halls, and community centers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The long-term ambition is to give communities the tools to preserve the human stories that official archives overlook.&lt;/p&gt;




&lt;h3&gt;
  
  
  Running Locally:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install Ollama and download the model:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama serve
ollama pull gemma3:4b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Clone and start the application:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/budulay/ghostlight-stadium-of-echoes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ghostlight-stadium-of-echoes
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open in your browser:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5173
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample memory can also be explored instantly without live AI inference. For real-time memory analysis, ensure Ollama is running with &lt;code&gt;gemma3:4b&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Whistle
&lt;/h2&gt;

&lt;p&gt;I started with a simple question:&lt;br&gt;&lt;br&gt;
&lt;em&gt;What if we could preserve the feeling of a football match, not just its score?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A stadium is far more than concrete, grass, and plastic seats.&lt;/p&gt;

&lt;p&gt;It is a place where generations meet. Where friendships begin. Where families forge traditions. Where complete strangers hug in the pouring rain when a goal crosses the line in the 93rd minute.&lt;/p&gt;

&lt;p&gt;And perhaps somewhere, in an old drawer, there is still a faded ticket from a match that nobody else remembers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That ticket deserves a place.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;That story deserves a light.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Football remembers the scores.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;GHOSTLIGHT remembers the people.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;The final whistle is not the end of a memory.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;🏟️ &lt;strong&gt;GHOSTLIGHT: Stadium of Echoes&lt;/strong&gt; — &lt;em&gt;Where memories never die.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>ollama</category>
    </item>
    <item>
      <title>TouchGrass FC: a Telegram bot whose only goal is to make you close Telegram</title>
      <dc:creator>πn</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:05:27 +0000</pubDate>
      <link>https://dev.to/ptbdtq/touchgrass-fc-a-telegram-bot-whose-only-goal-is-to-make-you-close-telegram-43lp</link>
      <guid>https://dev.to/ptbdtq/touchgrass-fc-a-telegram-bot-whose-only-goal-is-to-make-you-close-telegram-43lp</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every pickup football group has the same thread. "Saturday?" "Maybe." "What time?" "After work." "Which Saturday?" Twenty messages later, nobody has committed and nobody has played.&lt;/p&gt;

&lt;p&gt;TouchGrass FC is a Telegram group bot that replaces that thread with one question and one poll. Someone types &lt;code&gt;/play&lt;/code&gt;, and the bot asks everyone to reply with when they can make it, in their own words and whichever language they type in (it's built for Ukrainian, English, Spanish, German, Polish, French, Russian). A local Gemma model turns each reply into structured availability. From there it's plain code: check the 7-day forecast, drop any slot that wouldn't finish before sunset, pick the best one, find the nearest pitch on OpenStreetMap, and post exactly one poll with a map pin. Then the bot goes quiet, except for a reminder three hours before kickoff and one question afterwards: did you actually play?&lt;/p&gt;

&lt;p&gt;That's the whole design for this week's theme. Per player, the interaction is one message and one tap on the poll. Per match, it's one &lt;code&gt;/played&lt;/code&gt; from whoever remembers. The only metric I care about is the one &lt;code&gt;/stats&lt;/code&gt; reports: &lt;code&gt;⚽ Matches actually played via this bot: N&lt;/code&gt;. (The bot's reply to &lt;code&gt;/played&lt;/code&gt; is "Legendary. Logged. Go shower." Good advice, honestly.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Did anyone actually touch grass?
&lt;/h3&gt;

&lt;p&gt;Not yet, and I'd rather say so than invent a match. I've run the full loop in a live Telegram chat: messy replies in; weather score, pitch pin and poll out; reminder queued. But any number you see on &lt;code&gt;/stats&lt;/code&gt; in my demo comes from me pressing the button to test it, not from a game. I'll update this post after the first real one.&lt;/p&gt;

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

&lt;p&gt;The bot runs on my machine, so there's no live link. Here's a walkthrough in a real Telegram chat:&lt;/p&gt;

&lt;p&gt;No bot token? &lt;code&gt;python -m touchgrass simulate&lt;/code&gt; replays a scripted group chat in your terminal (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/budulay" rel="noopener noreferrer"&gt;
        budulay
      &lt;/a&gt; / &lt;a href="https://github.com/budulay/touchgrass-fc" rel="noopener noreferrer"&gt;
        touchgrass-fc
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Telegram group bot that turns messy "when can you play?" chatter into ONE poll for a real-life pickup football match — then goes quiet so people actually go outside.
    &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;touchgrass-fc&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A Telegram group bot that turns messy "when can you play?" chatter into ONE poll for a real-life pickup football match — then goes quiet so people actually go outside.&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/budulay/touchgrass-fc" 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;MIT licensed, and a brand-new repo started during the challenge window.&lt;/p&gt;

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

&lt;p&gt;One rule shaped everything: the model only gets the job code can't do, which is reading human messages. Everything else is boring, testable code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.11+, aiogram 3&lt;/strong&gt;: long polling (no server needed), privacy mode ON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 3 via Ollama&lt;/strong&gt;: local inference, with Pydantic v2 validating every output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite (aiosqlite)&lt;/strong&gt;: all state stays on the machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-Meteo, Nominatim, Overpass&lt;/strong&gt;: free and keyless; weather and map lookups are the only things that leave the machine&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reading messy messages
&lt;/h3&gt;

&lt;p&gt;People don't write schemas. "можу в суботу після 18, якщо не дощ", "Sat morning I'm in" and "пятница после обеда" all have to land in the same structure. Ollama's &lt;code&gt;format&lt;/code&gt; parameter accepts a JSON schema, so each request carries the Pydantic model's schema, at temperature 0 (abridged):&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Pydantic JSON schema
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the first message above, the output looks roughly like this (abridged):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"can_play"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slots"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"weekday"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"saturday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"18:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"no_rain"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uk"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If validation fails, the error goes back to the model together with the original message, once. If that fails too, the bot asks the player to rephrase instead of guessing. One gotcha worth passing on: Ollama's &lt;code&gt;format&lt;/code&gt; wants the complete JSON Schema object, not just the &lt;code&gt;$defs&lt;/code&gt; block that Pydantic nests sub-models under.&lt;/p&gt;

&lt;h3&gt;
  
  
  The planner is deliberately dumb
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;score = player_count × weather_score&lt;/code&gt;. Weather score is a 60% rain penalty plus a 40% wind penalty, averaged over the slot window. Any slot ending after sunset (with a 15-minute buffer) is filtered out before scoring. No LLM involved: it's just numbers, which means it's unit-tested on recorded fixtures instead of vibes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the model actually work?
&lt;/h3&gt;

&lt;p&gt;The repo has 40 labelled messages in Ukrainian, Russian and English: slang, negations, conditionals, vague ranges like "можу в будь-який день після 19". The eval harness runs them against each Gemma size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Overall&lt;/th&gt;
&lt;th&gt;can_play&lt;/th&gt;
&lt;th&gt;language&lt;/th&gt;
&lt;th&gt;slots&lt;/th&gt;
&lt;th&gt;conditions&lt;/th&gt;
&lt;th&gt;avg_lat&lt;/th&gt;
&lt;th&gt;p95_lat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma3:1b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;td&gt;45%&lt;/td&gt;
&lt;td&gt;48%&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;6.6s&lt;/td&gt;
&lt;td&gt;16.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma3:4b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;72%&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;23.2s&lt;/td&gt;
&lt;td&gt;36.1s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Overall column is the harshest number in the table, and I'm showing it anyway. Per field, &lt;code&gt;gemma3:4b&lt;/code&gt; is reasonable at what the planner needs most: whether someone can play (88%), under what conditions (88%), and roughly when (72%). Language detection sits around 50% for both sizes; it mostly decides which language the invite is written in, and I haven't dug into why it's so weak yet.&lt;/p&gt;

&lt;p&gt;Speed is the other cost. On my machine [TODO: one line on your hardware, e.g. "a laptop with no GPU"], &lt;code&gt;gemma3:4b&lt;/code&gt; takes 23 seconds per message on average (p95: 36 s). &lt;code&gt;gemma3:1b&lt;/code&gt; is about 3.5× faster but gets &lt;code&gt;can_play&lt;/code&gt; wrong more than half the time. So 4b is the default, and when it returns garbage the bot asks instead of guessing. For a group that coordinates once a week, I can live with 23 seconds. For anything conversational, I couldn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it without Telegram
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;python -m touchgrass simulate&lt;/code&gt; replays a scripted group conversation in your terminal, runs the real planner on synthetic weather, and prints a transcript. If Ollama is running, it uses the real model, so you'll wait as long as the numbers above suggest. If it isn't, it falls back to canned parses: you see the flow in seconds, but you're not seeing the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  What doesn't work (yet)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The pitch search finds pitches that exist in OpenStreetMap. It has no idea whether one is free or bookable.&lt;/li&gt;
&lt;li&gt;One location per chat, set with &lt;code&gt;/setlocation&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Privacy mode stays on, so the bot only reads replies to its own message and &lt;code&gt;/in&lt;/code&gt;. If you don't reply to the bot, as far as it knows you don't exist.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I didn't benchmark a hosted model, so I won't pretend to know by how much it would beat a 4B Gemma on accuracy or speed. My guess is easily. Here's what going open bought me instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nobody new reads the chat.&lt;/strong&gt; The messages already live in Telegram. The question is whether a second company's API also gets to read who's free on Thursday evenings. Here, inference runs on the same machine as the bot, so it doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A model I can pin, swap and measure.&lt;/strong&gt; I picked an exact model tag and ran the same 40 fixtures against two sizes; the table above is the result. Going from 1b to 4b is one environment variable (&lt;code&gt;OLLAMA_MODEL&lt;/code&gt;), and nothing changes underneath me between runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contributors don't need keys.&lt;/strong&gt; The extraction schema is a Pydantic model in &lt;code&gt;nlu.py&lt;/code&gt;, and the 29 tests run without network access. Anyone can change the schema, rerun the suite and open a PR without asking me, or a vendor, for credentials.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cost, honestly, isn't an argument at this scale: a few dozen inferences are cents on any hosted API. It only becomes one when a bot like this has real users.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built this in Google's Antigravity from one written brief, split into three parallel workstreams: language understanding plus the eval fixtures; planner, weather and pitch lookup; and the Telegram layer plus the simulator. The agent wrote the code, the 29 tests and the first draft of this post. I ran the result in a live Telegram chat, then edited this write-up.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt;: Gemma 3 (via Ollama) is the only model in the project. It does the multilingual message understanding and writes the two-sentence invite in the group's dominant language. It runs locally, and the repo benchmarks it at two sizes (&lt;code&gt;gemma3:1b&lt;/code&gt; and &lt;code&gt;gemma3:4b&lt;/code&gt;) on the same 40 fixtures. The results are in the eval section above, including the parts that don't flatter it.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The bot's best feature is that it knows when to stop talking. This post is trying to learn from it.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ollama</category>
      <category>python</category>
    </item>
    <item>
      <title>CustomsBuddy: Your customs broker for international parcels</title>
      <dc:creator>πn</dc:creator>
      <pubDate>Wed, 04 Mar 2026 20:06:54 +0000</pubDate>
      <link>https://dev.to/ptbdtq/customsbuddy-your-customs-broker-for-international-parcels-1fi0</link>
      <guid>https://dev.to/ptbdtq/customsbuddy-your-customs-broker-for-international-parcels-1fi0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/mlh/built-with-google-gemini-02-25-26"&gt;Built with Google Gemini: Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built with Google Gemini
&lt;/h2&gt;

&lt;p&gt;Sending international packages is a pain. Especially when it comes to filling out customs declarations (CN22/CN23).&lt;/p&gt;

&lt;p&gt;The average user doesn't know what "HS Codes" are, doesn't know how to correctly translate the names of specific items into English customs language, and often doesn't even realize that their favorite homemade cookies or cold pills are prohibited from being imported into the destination country.&lt;/p&gt;

&lt;p&gt;To solve this problem, I created &lt;strong&gt;CustomsBuddy&lt;/strong&gt; — a React application that converts a user's chaotic text description of a package into a rigorous, validated array of data.&lt;/p&gt;

&lt;p&gt;In this article, I'll show how we abandoned unreliable text parsing and used &lt;strong&gt;Structured Output (JSON Schema) and dynamic System Instructions&lt;/strong&gt; in the Gemini API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://customs-buddy-528833377593.us-central1.run.app/" rel="noopener noreferrer"&gt;Demo:&lt;/a&gt;&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag__cloud-run"&gt;
  &lt;iframe height="600px" src="https://customs-buddy-528833377593.us-central1.run.app"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  🛑 Why don't regular prompts work for UI?
&lt;/h2&gt;

&lt;p&gt;Let's imagine a typical input: "&lt;em&gt;I'm sending two used t-shirts, a new book, homemade cookies, and 50 bucks worth of medicine to Canada.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;If you ask a regular LLM to "make a table out of this," it will return Markdown. Markdown looks great in chat, but as a developer, I need to draw React components: green checkmarks for allowed items, red warnings for prohibited items, and calculate the total.&lt;/p&gt;

&lt;p&gt;I need predictable JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Solution: Gemini Structured Output
&lt;/h2&gt;

&lt;p&gt;Instead of begging the model to "please return only JSON," we use the &lt;code&gt;responseSchema&lt;/code&gt; parameter in the Gemini API. This forces the model to strictly follow the structure we specify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Ideal Structure (The Schema)
&lt;/h2&gt;

&lt;p&gt;Here is the configuration object that we will pass to the API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const responseSchema = {
  type: "OBJECT",
  properties: {
    items: {
      type: "ARRAY",
      items: {
        type: "OBJECT",
        properties: {
          original_description: { type: "STRING" },
          customs_description_en: { type: "STRING" },
          hs_code: { type: "STRING" }, // International customs code
          quantity: { type: "INTEGER" },
          weight_kg: { type: "NUMBER" },
          value_usd: { type: "NUMBER" },
          is_allowed: { type: "BOOLEAN" }, // The main trigger for UI!
          warning_reason: { type: "STRING" } // Explanation if prohibited
        },
        required: ["original_description", "customs_description_en", "hs_code", "quantity", "weight_kg", "value_usd", "is_allowed", "warning_reason"]
      }
    },
    general_warnings: {
      type: "ARRAY",
      items: { type: "STRING" }
    }
  },
  required: ["items", "general_warnings"]
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Dynamic System Instructions
&lt;/h2&gt;

&lt;p&gt;Our app supports different destination countries (USA, Canada, Germany, etc.) and different interface languages. The US customs rules are different from Germany. That's why we make the &lt;code&gt;systemInstruction&lt;/code&gt; dynamic!&lt;/p&gt;

&lt;p&gt;In the React component, before calling the API, we form the instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Get the country name in English and the language for the explanation
const destCountryEn = countries.find(c =&amp;gt; c.code === destination).name.en;
const explanationLang = lang === 'uk' ? 'українською мовою' : 'in English';

const systemInstruction = `You are a professional international customs broker.
Your task: to analyze user input to send parcels to the country: ${destCountryEn}.
1. Translate the product descriptions into strict, standardized English.
2. Determine the exact 6-digit HS Code.
3. Logically distribute the cost/weight if only the total is specified.
4. STRONG VALIDATION: Check the customs rules of the country ${destCountryEn}. If the item is prohibited (e.g., homemade food, certain medications), set is_allowed: false and explain why, ${explanationLang} is mandatory. Respond strictly in JSON format.`;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Calling the Gemini API
&lt;/h2&gt;

&lt;p&gt;Now we send all this to &lt;code&gt;gemini-2.5-flash&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
  contents: [{ parts: [{ text: inputText }] }],
  systemInstruction: { parts: [{ text: systemInstruction }] },
  generationConfig: {
    responseMimeType: "application/json",
    responseSchema: responseSchema // Inserting our schema!
  }
 })
});

const data = await response.json();
const jsonText = data.candidates[0].content.parts[0].text;
const parsedDeclaration = JSON.parse(jsonText);
// That's it! No regular expressions or text truncation!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎯 How does this look in practice?
&lt;/h2&gt;

&lt;p&gt;When a user types: "&lt;em&gt;I'm sending two T-shirts, a book, homemade cookies, and $50 worth of cold medicine to Canada&lt;/em&gt;"...&lt;/p&gt;

&lt;p&gt;Gemini returns a perfect JSON where for the book and T-shirts &lt;code&gt;is_allowed: true&lt;/code&gt; (and the model itself found the HS codes &lt;code&gt;4901.99&lt;/code&gt; and &lt;code&gt;6309.00&lt;/code&gt; for them). But for the cookies and medicine, the model returns &lt;code&gt;is_allowed: false&lt;/code&gt; and adds an explanation that Canadian customs blocks homemade food without factory labeling and over-the-counter medicines.&lt;/p&gt;

&lt;p&gt;In React, we simply map this array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{results.items.map((item, idx) =&amp;gt; ( 
 &amp;lt;div key={idx} className={item.is_allowed ? 'bg-slate-50' : 'bg-red-50'}&amp;gt; 
  {item.is_allowed ? &amp;lt;CheckCircle2 className="text-emerald-500" /&amp;gt; : &amp;lt;AlertOctagon className="text-red-500" /&amp;gt;} 

  &amp;lt;h3&amp;gt;{item.customs_description_en}&amp;lt;/h3&amp;gt; 
  &amp;lt;span&amp;gt;HS Code: {item.hs_code}&amp;lt;/span&amp;gt; 

  {!item.is_allowed &amp;amp;&amp;amp; ( 
  &amp;lt;div className="text-red-700"&amp;gt; 
   Failure reason: {item.warning_reason} 
  &amp;lt;/div&amp;gt; 
  )} 
 &amp;lt;/div&amp;gt;
))}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💡 Conclusion
&lt;/h2&gt;

&lt;p&gt;Structured Output (JSON Schema) completely changes the approach to developing AI applications. We no longer use LLMs simply as "smart interlocutors". We use them as reliable microservices for data analysis, classification and transformation that integrate perfectly into our frontend.&lt;/p&gt;

&lt;p&gt;Try adding &lt;code&gt;responseSchema&lt;/code&gt; to your next Gemini project - and you will forget about the pain of text parsing forever!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>geminireflections</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Maplewood Heights (Community Connect)</title>
      <dc:creator>πn</dc:creator>
      <pubDate>Mon, 02 Mar 2026 07:55:04 +0000</pubDate>
      <link>https://dev.to/ptbdtq/maplewood-heights-community-connect-25fo</link>
      <guid>https://dev.to/ptbdtq/maplewood-heights-community-connect-25fo</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today I’m excited to introduce Maplewood Heights, a warm and inviting hub for our local community! &lt;/p&gt;

&lt;p&gt;🏡 It’s a place where neighbors can learn about events, share news, and discover local gems.&lt;/p&gt;

&lt;p&gt;☁️ I'd love to hear your thoughts on the design!&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag__cloud-run"&gt;
  &lt;iframe height="600px" src="https://community-connect-528833377593.us-central1.run.app"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;p&gt;&lt;em&gt;The app is already deployed to Google Cloud:&lt;/em&gt;: &lt;a href="https://community-connect-528833377593.us-central1.run.app" rel="noopener noreferrer"&gt;https://community-connect-528833377593.us-central1.run.app&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;🛠️ &lt;strong&gt;Functionality of the first version already has a lot of useful features:&lt;/strong&gt;&lt;br&gt;
📅 View upcoming events &lt;br&gt;
💬 Live feed of community updates &lt;br&gt;
☕ Favorite local places &lt;br&gt;
🤝 Neighbor profiles and statistics&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updates feed: Multi-level comment threads, quick reactions, and emojis. &lt;/li&gt;
&lt;li&gt;Community: Get to know your neighbors and quickly message them. &lt;/li&gt;
&lt;li&gt;Events: A board of upcoming activities (from farmers' markets to volunteering). &lt;/li&gt;
&lt;li&gt;Local "gems": Recommendations for the best bakeries, parks, or coffee shops in the area.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My main goal was to combine the speed of modern frontend technologies with a stylish, warm UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack &amp;amp; Architecture:&lt;/strong&gt; &lt;br&gt;
⚡ React 18 + Vite (quick build via SWC) &lt;br&gt;
🎨 Tailwind CSS v3 + CSS-variables (fully custom light/dark theme in honey/sage green tones) &lt;br&gt;
🧱 shadcn/ui (integrated ~50 Radix UI components) &lt;br&gt;
✨ Framer Motion (controls orchestrating animations when scrolling and microinteractions) &lt;br&gt;
🚢 &lt;strong&gt;CI/CD:&lt;/strong&gt; Multi-stage Docker build on top of Nginx -&amp;gt; deployment to Google Cloud Run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's inside?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watercolor hero block.&lt;/li&gt;
&lt;li&gt;Update feed with support for recursive N-level comments and reactions.&lt;/li&gt;
&lt;li&gt;Neighborhood mutual aid module.&lt;/li&gt;
&lt;li&gt;Own context-messaging right in the browser.&lt;/li&gt;
&lt;li&gt;When design (DM Serif Display + warm tones) works in synergy with cool UX, users really want to come back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Happy to connect people through technology.&lt;/strong&gt; ✨&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>My Portfolio Has a Split Personality: GUI vs Terminal</title>
      <dc:creator>πn</dc:creator>
      <pubDate>Mon, 02 Feb 2026 07:58:24 +0000</pubDate>
      <link>https://dev.to/ptbdtq/my-portfolio-has-a-split-personality-gui-vs-terminal-3olc</link>
      <guid>https://dev.to/ptbdtq/my-portfolio-has-a-split-personality-gui-vs-terminal-3olc</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/new-year-new-you-google-ai-2025-12-31"&gt;New Year, New You Portfolio Challenge Presented by Google AI&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hello, network! 👋&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the "&lt;strong&gt;New Year, New You&lt;/strong&gt;" challenge, I didn't want to just update my CSS. I wanted to build something that represents the duality of a &lt;em&gt;Full Stack Developer&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;The Professional: &lt;em&gt;Clean, modern UI using the trendy Bento Grid layout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Geek: &lt;em&gt;A hacker-style Terminal for those who prefer CLI over GUI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out!&lt;/strong&gt;&lt;br&gt;
Check out the live demo and let me know: are you team #GUI or team #Terminal? 👇&lt;br&gt;


&lt;/p&gt;
&lt;div class="ltag__cloud-run"&gt;
  &lt;iframe height="600px" src="https://devportfolio-528833377593.us-central1.run.app/"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Available at&lt;/em&gt;: &lt;a href="https://devportfolio-528833377593.us-central1.run.app/" rel="noopener noreferrer"&gt;https://devportfolio-528833377593.us-central1.run.app/&lt;/a&gt; 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Concept&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Usually, portfolios are static. You read, you scroll, you leave. I wanted interaction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GUI Mode:&lt;/strong&gt; Visualizes my projects and skills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal Mode:&lt;/strong&gt; Lets you type commands like &lt;code&gt;help&lt;/code&gt;, &lt;code&gt;skills&lt;/code&gt;, or even &lt;code&gt;ask&lt;/code&gt; to talk to AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The AI Magic ✨&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of searching for my experience in a long text, recruiters can now ask:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Tell me about your skills?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AI analyzes my JSON data and generates a professional answer instantly. It lives both in a chat modal (GUI) and as a command (Terminal).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tech Stack&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; React + Vite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI:&lt;/strong&gt; Google Gemini API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Icons:&lt;/strong&gt; Lucide React&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most time was spent not on the code, but on making the site feel "like me": a little mysterious, a little playful, but at the same time very professional.&lt;/p&gt;

&lt;p&gt;This is not just a portfolio. This is my manifesto for 2026: code with passion, use the best tools (thanks Google AI) and don't be afraid to be yourself.&lt;/p&gt;

&lt;p&gt;Thanks for stopping by.&lt;/p&gt;

&lt;p&gt;Write in the comments what you liked/what you would change - I'd be happy to hear your feedback.&lt;br&gt;
And if you also participated - drop a link, I'll take a look and like 🚀&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>portfolio</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
