<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Min Bright]]></title><description><![CDATA[Min Bright]]></description><link>https://minbright.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 15:24:52 GMT</lastBuildDate><atom:link href="https://minbright.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Open-source software does not make every AI workflow local or free]]></title><description><![CDATA[An open-source creative app can still call a paid model provider. A desktop download can still send generation requests to a server. Those are different…]]></description><link>https://minbright.hashnode.dev/open-source-software-does-not-make-every-ai-workflow-local-or-free</link><guid isPermaLink="true">https://minbright.hashnode.dev/open-source-software-does-not-make-every-ai-workflow-local-or-free</guid><dc:creator><![CDATA[Min Bright]]></dc:creator><pubDate>Wed, 21 Oct 2026 02:32:26 GMT</pubDate><content:encoded><![CDATA[<p>An open-source creative app can still call a paid model provider. A desktop download can still send generation requests to a server. Those are different properties, and a download guide should keep them separate.</p>
<p>ArtCraft is a useful example. Its upstream README describes an IDE for interactive AI image and video creation, including 2D composition, 3D scene staging and a catalog of model providers. Its source is available under MIT or Apache-2.0, with separate notices for third-party material.</p>
<h2>Separate the software license from the generation service</h2>
<p>The app's code license tells you how you may use and redistribute that code. It does not establish the price, availability or output rights of every model the app can access.</p>
<p>The current upstream README lists providers including Grok, Midjourney, Sora and World Labs. It also marks some catalog entries as disabled or restricted. Treat that catalog as a changing integration list, not a promise that every model runs on your GPU.</p>
<h2>Separate the studio from the other Crafting Apps</h2>
<p>The official site also has a Crafting Apps section describing native open-source editors for images, vectors, CAD and other work. Those apps have their own development status and downloads.</p>
<p>A local editor working on your files and an AI generation service are not interchangeable. Before calling a workflow offline, check the specific app, operation, model and version. The existence of a local image editor does not establish that an image-generation request stays local.</p>
<h2>A checklist before publishing a setup guide</h2>
<ol>
<li><p>Link to the upstream release and identify the app you mean.</p>
</li>
<li><p>Record the version and date for platform and feature claims.</p>
</li>
<li><p>Distinguish editing on the device from model inference.</p>
</li>
<li><p>Check model-provider pricing and terms separately from the app license.</p>
</li>
<li><p>Do not turn a minimum hardware suggestion into a measured speed claim.</p>
</li>
<li><p>Label affiliate recommendations as affiliate recommendations, not required dependencies.</p>
</li>
</ol>
<p>A guide can still be useful without a claim like "everything is free forever." Clear boundaries save readers from installing a tool for a workflow it does not provide.</p>
<p>I run <a href="https://getartcraft.net/">ArtCraft Guide</a>, an independent guide site, not the upstream project. This distinction between software, models and services is the standard a third-party guide needs to meet.</p>
<p>Sources, checked October 21, 2026:</p>
<ul>
<li><p><a href="https://github.com/storytold/artcraft">github.com/storytold/artcraft</a></p>
</li>
<li><p><a href="https://getartcraft.com/">getartcraft.com</a></p>
</li>
<li><p><a href="https://getartcraft.com/apps">getartcraft.com/apps</a></p>
</li>
</ul>
<p>Disclosure: I operate the linked guide site. It contains affiliate and referral links. I do not develop ArtCraft, and this article is a source-based explanation, not a claim of hands-on benchmarking.</p>
]]></content:encoded></item><item><title><![CDATA[The iframe details that make or break an embedded browser game]]></title><description><![CDATA[A browser game can work in its own tab and still be awkward inside another page. Focus, fullscreen and sizing become part of the integration. This is a…]]></description><link>https://minbright.hashnode.dev/the-iframe-details-that-make-or-break-an-embedded-browser-game</link><guid isPermaLink="true">https://minbright.hashnode.dev/the-iframe-details-that-make-or-break-an-embedded-browser-game</guid><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Min Bright]]></dc:creator><pubDate>Tue, 20 Oct 2026 02:30:33 GMT</pubDate><content:encoded><![CDATA[<p>A browser game can work in its own tab and still be awkward inside another page. Focus, fullscreen and sizing become part of the integration.</p>
<p>This is a code-level look at <a href="https://martinfightinggame.net/">martinfightinggame.net</a>, a site I run. The game is Martin: No Filter Fighters, created by TDHampton Productions and published by Trey2k23 on itch.io. I run the independent play page and guide, not the game studio. These notes describe the page code, not a cross-browser test report.</p>
<h2>Same-origin is a tradeoff</h2>
<p>The game iframe points at <code>/game-proxy/index.html</code> on the same domain. Same-origin framing allows parent and child DOM access, subject to sandbox restrictions. A separate origin provides isolation that this setup does not.</p>
<p>An iframe URL is not evidence of a license. A free-to-play release does not establish permission to redistribute a game and its assets.</p>
<h2>Read the permissions, but do not overpromise</h2>
<p>The iframe declares <code>allow="autoplay; fullscreen; gamepad; focus-without-user-activation *"</code> and <code>allowfullscreen="true"</code>.</p>
<p>That list is configuration, not proof every feature works. MDN documents the gamepad policy's default allowlist as <code>*</code>, so omitting an explicit gamepad token does not always disable controllers. Browser support and the parent page's Permissions-Policy still matter.</p>
<p>An autoplay declaration does not override browser audio restrictions. A real play action is a better point to start audio. The presence of a focus-related token should not replace testing clicks and keyboard input.</p>
<h2>Make the handoff to the game visible</h2>
<p>The iframe has <code>tabindex="0"</code>. The start action dismisses the loading overlay and attempts to call <code>contentWindow.focus()</code>; the reload action also attempts to focus the game.</p>
<p>A useful test sequence is to start, click outside, return to the game, and try the controls again. Include keyboard-only navigation. The page also prevents arrow-key and space scrolling when the iframe is active or the page is in fullscreen. Keep that condition narrow so normal page navigation still works outside play.</p>
<h2>Reserve space, then measure it</h2>
<p>The wrapper combines a 16/9 aspect-ratio class with minimum heights of 420px and 540px at different breakpoints. The iframe fills that wrapper and uses <code>loading="eager"</code>.</p>
<p>A minimum height can override the height suggested by an aspect ratio. A class named 16/9 does not guarantee a 16:9 box at every width. Inspect the rendered dimensions on narrow screens and check whether the game scales, crops or letterboxes.</p>
<h2>Fullscreen needs a deliberate action</h2>
<p>The cabinet button requests fullscreen on the game wrapper. A separate global control requests it on the document. The mobile landscape action attempts fullscreen and then an orientation lock, catching failures.</p>
<p>MDN documents <code>requestFullscreen()</code> as requiring transient user activation. Its promise can reject, so keep a usable non-fullscreen layout. A visible button is easier to discover than a keyboard shortcut alone.</p>
<h2>Loading feedback should say what it measures</h2>
<p>The loading bar advances on a timer, with random increments until 88%, then reaches 100% in the iframe load handler. That is simulated progress, not measured download progress or proof the game has finished initializing.</p>
<p>A child-to-parent ready signal would give a clearer readiness boundary. If measured progress is unavailable, an indeterminate message is more honest than a precise-looking percentage.</p>
<h2>Give the surrounding page a job</h2>
<p>Controls and character guides should help someone play and find the creator, rather than repeat a title around an iframe.</p>
<p>The example page is <a href="https://martinfightinggame.net/">martinfightinggame.net</a>. The <a href="https://trey2k23.itch.io/martin-no-filter-fighters-unofficial-fan-game">creator's original release</a> is on itch.io.</p>
<p>References: <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe">MDN iframe</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen">requestFullscreen</a>, and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy/gamepad">gamepad policy</a>.</p>
<p>Disclosure: I operate martinfightinggame.net. This article promotes my site while crediting the game's creator. It does not claim that I developed the game or hold rights to its characters or assets.</p>
]]></content:encoded></item><item><title><![CDATA[Building a perfume finder that can't lie: catalog-driven matching instead of LLM vibes]]></title><description><![CDATA[I recently shipped ScentAI ( a perfume finder, and the core design decision was unusual for 2026: the AI is not allowed to invent anything.]]></description><link>https://minbright.hashnode.dev/building-a-perfume-finder-that-can-t-lie-catalog-driven-matching-instead-of-llm-vibes</link><guid isPermaLink="true">https://minbright.hashnode.dev/building-a-perfume-finder-that-can-t-lie-catalog-driven-matching-instead-of-llm-vibes</guid><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[product]]></category><dc:creator><![CDATA[Min Bright]]></dc:creator><pubDate>Mon, 19 Oct 2026 02:40:32 GMT</pubDate><content:encoded><![CDATA[<p>I recently shipped ScentAI (<a href="https://getscentai.com">https://getscentai.com</a>), a perfume finder, and the core design decision was unusual for 2026: the AI is not allowed to invent anything.</p>
<h2>The problem</h2>
<p>Ask a general-purpose LLM for a perfume recommendation and you'll get confident, plausible answers - with notes that don't exist, dupes it can't prove, and "facts" assembled from marketing copy. Fragrance is unusually vulnerable to this because most of us can't verify a note pyramid by memory.</p>
<h2>The approach</h2>
<p>ScentAI grounds everything in a catalog: accords, top/heart/base notes, and concentration for each fragrance. The AI finder takes a plain-language request ("something green and quiet for the office") and resolves it against real catalog entries - the output is ranked matches with the reasons shown, and every reason points back to a catalog field. If a detail isn't in the catalog, the UI says so instead of papering over it.</p>
<h2>The similarity engine</h2>
<p>Starting from a perfume you own, ScentAI computes accord and note overlap and shows the percentage on every comparison. Two formulas can be compared side by side. This is my answer to the internet's favorite fragrance question - "what's a dupe for X?" - without claiming clones I can't prove.</p>
<h2>Anonymous votes</h2>
<p>Like / own / want / tried votes need no account and feed the trending lists. Accounts are the default growth lever for products like this; I wanted to see how far I could get without one.</p>
<h2>What I learned</h2>
<p>Constraining the AI to a catalog costs coverage (the catalog is always smaller than the world) but buys trust. Users forgive "I don't know" far more readily than a confident wrong answer.</p>
<p>Disclosure: I built ScentAI and this post describes my own product. The site contains affiliate links - I may earn a commission if you buy through them.</p>
<p>Try it: <a href="https://getscentai.com">https://getscentai.com</a> - curious what this community thinks of the catalog-constrained approach.</p>
]]></content:encoded></item><item><title><![CDATA[I added a WCAG contrast checker to a random color generator - here's the math]]></title><description><![CDATA[Like a lot of frontend devs, I use random color generators for mockups and placeholder themes. And like a lot of frontend devs, I've shipped text on those…]]></description><link>https://minbright.hashnode.dev/i-added-a-wcag-contrast-checker-to-a-random-color-generator-here-s-the-math</link><guid isPermaLink="true">https://minbright.hashnode.dev/i-added-a-wcag-contrast-checker-to-a-random-color-generator-here-s-the-math</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Min Bright]]></dc:creator><pubDate>Sun, 18 Oct 2026 01:41:04 GMT</pubDate><content:encoded><![CDATA[<p>Like a lot of frontend devs, I use random color generators for mockups and placeholder themes. And like a lot of frontend devs, I've shipped text on those colors that was basically unreadable.</p>
<p>So I built a random color generator with the accessibility check built in: <a href="https://randomcolorgenerator.app">randomcolorgenerator.app</a> - every color it generates instantly shows its WCAG 2.1 contrast ratio against white (#FFFFFF) and dark (#0F172A), with AA/AAA badges.</p>
<h2>How the contrast ratio works</h2>
<p>WCAG 2.1 defines contrast ratio as (L1 + 0.05) / (L2 + 0.05), where L1 and L2 are the relative luminances of the lighter and darker colors. Relative luminance comes from the sRGB channels:</p>
<ol>
<li><p>Convert each channel to the 0-1 range.</p>
</li>
<li><p>Linearize: if c &lt;= 0.03928 then c / 12.92, otherwise ((c + 0.055) / 1.055) ^ 2.4</p>
</li>
<li><p>L = 0.2126 * R + 0.7152 * G + 0.0722 * B</p>
</li>
</ol>
<p>In JavaScript:</p>
<pre><code class="language-javascript">function luminance(r, g, b) {
  const f = (c) =&gt; {
    c /= 255;
    return c &lt;= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  };
  return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
}

function contrastRatio(l1, l2) {
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}
</code></pre>
<p>The thresholds to remember: 4.5:1 for AA normal text, 3:1 for AA large text (18pt+, or 14pt bold), and 7:1 for AAA normal text.</p>
<h2>Two things I learned building it</h2>
<ul>
<li><p>Mid-luminance colors are the danger zone. A hue that looks "clearly different" from your background can still sit under 3:1.</p>
</li>
<li><p>Checking against both a white and a dark surface matters - a color that passes on one often fails on the other.</p>
</li>
</ul>
<p>The site also has one-click HEX/RGB/HSL copy, a palette generator, shareable color URLs, and a guess-the-HEX game. Free, no signup, commercial use fine. Feedback very welcome - what would make this more useful in your workflow?</p>
]]></content:encoded></item></channel></rss>