<?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: Humayoun Baig</title>
    <description>The latest articles on DEV Community by Humayoun Baig (@humayounbaig).</description>
    <link>https://dev.to/humayounbaig</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%2F502735%2F3ccaf82e-4cfd-457c-9994-5c6bdf39869b.jpeg</url>
      <title>DEV Community: Humayoun Baig</title>
      <link>https://dev.to/humayounbaig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/humayounbaig"/>
    <language>en</language>
    <item>
      <title>Removing a streaming overlay without blacking out the video</title>
      <dc:creator>Humayoun Baig</dc:creator>
      <pubDate>Tue, 06 Oct 2026 15:20:42 +0000</pubDate>
      <link>https://dev.to/humayounbaig/removing-a-streaming-overlay-without-blacking-out-the-video-21im</link>
      <guid>https://dev.to/humayounbaig/removing-a-streaming-overlay-without-blacking-out-the-video-21im</guid>
      <description>&lt;p&gt;Streaming apps often lay a dark gradient over the picture so titles and controls stay readable. I wanted that layer gone, not the movie. The result is ClearView, a small Chrome extension with a slider from the original shade to fully removed.&lt;/p&gt;

&lt;p&gt;Apart from Netflix almost all major streaming service has this annoying blur on hover that takes too long to disappear for my liking.&lt;/p&gt;

&lt;p&gt;The extension is still a WIP but can be used by cloning the repo for now, &lt;br&gt;
Tested on HBO Max, And Apple TV&lt;/p&gt;
&lt;h2&gt;
  
  
  Apple TV is a bit of an hassle to get right...for now
&lt;/h2&gt;

&lt;p&gt;At 100% reduction, tv.apple.com went black. The extension had found a dark element the size of the player and set its opacity to 0. That element was not the shade. It was a parent of the video. Fading it faded the picture too, and the page behind the player is black.&lt;/p&gt;

&lt;p&gt;Two details in Apple's player make this easy to get wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; sits in an open shadow root, so &lt;code&gt;document.querySelector("video")&lt;/code&gt; never sees it.&lt;/li&gt;
&lt;li&gt;The shade is a full-size &lt;code&gt;::after&lt;/code&gt; gradient on an ancestor. &lt;code&gt;elementsFromPoint()&lt;/code&gt; does not return pseudo-elements, and &lt;code&gt;Node.contains()&lt;/code&gt; does not cross the shadow boundary, so a parent can look like a harmless overlay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/k_ivanow"&gt;Kiril Ivanov&lt;/a&gt; makes the case for a persistent content script when a page keeps mutating, rather than a one-shot &lt;code&gt;chrome.scripting&lt;/code&gt; injection. &lt;a href="https://dev.to/yuanjzhou"&gt;Yuan Zhou&lt;/a&gt; is blunter about the cost: on a site you do not control, you only see the DOM, the markup will change, and a content script that assumes the wrong shape can break the host page. Both showed up while I was deciding not to hard-code one streaming service.&lt;/p&gt;
&lt;h2&gt;
  
  
  The rule that fixed it
&lt;/h2&gt;

&lt;p&gt;Never change the opacity of an element that owns the video, including ownership that crosses a shadow root. Fade the gradient pseudo-element instead.&lt;/p&gt;

&lt;p&gt;Finding the video means walking open shadow roots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;collectVideos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;collectVideos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;found&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="nx"&gt;Ownership&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;follow&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;same&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;light&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;DOM&lt;/span&gt; &lt;span class="nx"&gt;ancestor&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;hosted&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ownsMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRootNode&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;ShadowRoot&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;You cannot set a pseudo-element's opacity from element.style. A content-script stylesheet can, and the slider only changes a custom property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;clearview&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pseudo&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;after&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;clearview&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;important&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;The script marks an ancestor only when ::before or ::after is absolutely positioned, covers most of that ancestor, and paints a dark translucent color or a dark gradient. Separate overlay elements that do not contain the video still use normal opacity. Interactive controls are left alone. There is a click-to-hide picker for the ones you do want gone.&lt;/p&gt;

&lt;p&gt;On the I, Robot title page, the gradient's computed opacity went to 0 while the video and its black wrapper stayed at 1. The artwork stayed on screen.&lt;/p&gt;

&lt;p&gt;What I would not do again&lt;br&gt;
I would not treat "dark and about the size of the video" as enough. A player shell is often exactly that, and its background is the letterbox, not the scrim. I would also not observe attribute mutations on a busy player. Kouta hit an infinite callback loop in Gmail by watching attributes. Child-list changes, debounced, were enough here too.&lt;/p&gt;

&lt;p&gt;Settings stay in chrome.storage.local, per site. Nothing about the page is sent anywhere.&lt;/p&gt;

&lt;p&gt;The source is here: &lt;a href="https://github.com/HumayounBaig/overlay-blocker-chrome-ext" rel="noopener noreferrer"&gt;HumayounBaig/overlay-blocker-chrome-ext&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>chrome</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Marigold: Hacktoberfest Weekend Challenge</title>
      <dc:creator>Humayoun Baig</dc:creator>
      <pubDate>Sat, 03 Oct 2026 22:26:57 +0000</pubDate>
      <link>https://dev.to/humayounbaig/marigold-hacktoberfest-weekend-challenge-1d7a</link>
      <guid>https://dev.to/humayounbaig/marigold-hacktoberfest-weekend-challenge-1d7a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;a href="https://github.com/HumayounBaig/marigold" rel="noopener noreferrer"&gt;Marigold&lt;/a&gt; for my wife.&lt;br&gt;
The weeks after birth are full of two problems that get treated badly online: pain, and the pressure to “drop the baby weight.” Her back, pelvis, incision or perineum, neck, and wrists take a daily beating from feeding and carrying. At the same time, a lot of apps answer that with a calorie target. That is a bad answer if she is healing, and a worse one if she is feeding a baby with her body.&lt;br&gt;
Marigold is a private recovery companion. Each day she logs sleep, energy, mood, where it hurts, bleeding, and a few symptoms that should never be coached through. An open-weight model on our computer writes a short plan from a fixed catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one gentle movement that matches how far she is from birth, whether a clinician has cleared more than daily life, and what hurts today&lt;/li&gt;
&lt;li&gt;meals that keep her fed, including a snack when she is nursing, pumping, or running on empty&lt;/li&gt;
&lt;li&gt;two comfort steps for the loudest pain&lt;/li&gt;
&lt;li&gt;a plain instruction to call a clinician when the check-in says so
Weight can be written down. It is not allowed to be the loudest thing in the room. If the number is falling faster than about a pound a week while she is still feeding the baby with her body, Marigold tells her to eat the snack and keep the walks easy.
Her notes stay in the browser on that machine. There is no account.
## Demo
There is no hosted demo. The model is the point, and it runs at home.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/HumayounBaig/marigold.git
&lt;span class="nb"&gt;cd &lt;/span&gt;marigold
npm &lt;span class="nb"&gt;install
&lt;/span&gt;ollama serve
ollama pull qwen2.5:3b
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;a href="http://127.0.0.1:43123" rel="noopener noreferrer"&gt;http://127.0.0.1:43123&lt;/a&gt;.&lt;br&gt;
The first screen asks for her name, the baby’s birth date, how she gave birth, how she is feeding, and whether a clinician has cleared more movement. After that, Today is a check-in. Move, Nourish, Soothe, and Progress are the rest of the house: the session player, the plates, the pain notes, and an optional weight record.&lt;br&gt;
If Ollama is asleep, the same catalog still builds the day and says so. The check-in still never leaves the computer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HumayounBaig/marigold" rel="noopener noreferrer"&gt;https://github.com/HumayounBaig/marigold&lt;/a&gt;&lt;br&gt;
&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/HumayounBaig" rel="noopener noreferrer"&gt;
        HumayounBaig
      &lt;/a&gt; / &lt;a href="https://github.com/HumayounBaig/marigold" rel="noopener noreferrer"&gt;
        marigold
      &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;Marigold&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Marigold is a private postpartum recovery companion. It was made for one person: the weeks after birth, when weight is changing, pain is ordinary and sometimes not, and the last thing she needs is an account on someone else’s server.&lt;/p&gt;

&lt;p&gt;Each day she logs sleep, energy, mood, where it hurts, and a few symptoms that should never be coached through. An open-weight model, running on the same computer, writes a short plan from a fixed catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a movement small enough for her stage, clearance, and today’s pain&lt;/li&gt;
&lt;li&gt;meals that keep recovery fed, including a snack when she is nursing or running on empty&lt;/li&gt;
&lt;li&gt;two comfort practices for the places that hurt&lt;/li&gt;
&lt;li&gt;a plain instruction to call a clinician when the check-in says so&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weight can be recorded. The app will not set a calorie target, and it will say so if the loss is fast enough to threaten energy…&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/HumayounBaig/marigold" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;&lt;br&gt;
The pieces that matter:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/lib/catalog.ts&lt;/code&gt; holds the movements, meals, and comfort steps. The model is not allowed to invent a workout.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/lib/recovery.ts&lt;/code&gt; decides what she is allowed to do, writes the prompt, and throws away model output that leaves the catalog or starts talking about deficits.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/app/api/plan/route.ts&lt;/code&gt; sends that prompt only to Ollama on &lt;code&gt;127.0.0.1&lt;/code&gt;.
## How I Built It
The open model is &lt;a href="https://huggingface.co/Qwen/Qwen2.5-3B-Instruct" rel="noopener noreferrer"&gt;Qwen2.5 3B Instruct&lt;/a&gt;, Apache-2.0 weights, served by &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; on the same machine as the app. A day plan is one local chat completion. No cloud model is in the path.
The app around it is Next.js, TypeScript, and Tailwind. She gets a check-in, a session player, a meal list, a pain guide, and a quiet weight record. Safety is not left to the prompt. Before the model speaks, the server drops anything she should not be offered yet: core work before clearance, bridges and squats when the pelvis is loud, walks when bleeding is more than light, and every workout when the check-in includes heavy bleeding, chest pain, a severe headache, a one-sided swollen calf, fever, a worrying incision, or thoughts of harm. Those days become rest, and the note points to a person. In the US that includes 988 and Postpartum Support International at 1-800-944-4773.
The model has to answer in JSON and may only use ids from that day’s allowed list. If it picks two snacks and no plate, or writes “cut calories,” or names a session she will not actually do, that part is replaced. The explanation has to name the movement that remains.
Another open model is one environment variable: &lt;code&gt;OLLAMA_MODEL&lt;/code&gt;. The behavior she gets is the catalog and &lt;code&gt;systemPrompt()&lt;/code&gt; in &lt;code&gt;src/lib/recovery.ts&lt;/code&gt;, which we can edit.
## Why Does Open Innovation Matter?
This is health information from the hardest weeks of a marriage: pain, bleeding, mood, feeding, weight. A closed coaching app would upload that to a company we do not control, then answer with a model we cannot open, swap, or tell to refuse a calorie target.
The open setup made four things possible.
It runs after the weights are downloaded, with no internet and no per-day bill. The first note takes a little while on a laptop CPU. Later notes are a few seconds. That is an acceptable cost for a plan she might make at 2 a.m.
Her data stays in browser storage. The plan request goes to our own Next.js server, which talks only to Ollama on localhost. A closed API cannot make that promise, because the product is the upload.
We can change the agent. Sessions, meals, and the system prompt are files. When the small model wrote fluffy encouragement, or tried to skip the real meal, the fix was a rule in our code, not a support ticket. Qwen still chooses among the safe options. It does not get to invent medicine, a fast, or a target weight.
It cost nothing beyond the computer we already had. A postpartum subscription would have charged us to hold the same notes.
Marigold is not a clinician, a pelvic floor therapist, or emergency care. Open weights do not make it one. What they made possible is a companion that will sit on our machine, take the pain seriously, and refuse to turn her recovery into a diet.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Built in Cursor. The session is here:&lt;br&gt;
&lt;a href="https://cursor.com/agents/bc-76c9d47c-407c-412c-91a5-c6132dfd58db" rel="noopener noreferrer"&gt;https://cursor.com/agents/bc-76c9d47c-407c-412c-91a5-c6132dfd58db&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This is an entry for the overall challenge. I did not use a partner product, so I am not entering a partner category.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
