<?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[Luke builds]]></title><description><![CDATA[Luke builds]]></description><link>https://luke-builds.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:11:16 GMT</lastBuildDate><atom:link href="https://luke-builds.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Selling one video without an account]]></title><description><![CDATA[AIRap turns photos into short rap and singing videos based on viral moments. When I designed it, I made two decisions that shaped most of the backend…]]></description><link>https://luke-builds.hashnode.dev/selling-one-video-without-an-account</link><guid isPermaLink="true">https://luke-builds.hashnode.dev/selling-one-video-without-an-account</guid><category><![CDATA[SaaS]]></category><category><![CDATA[Product Design]]></category><category><![CDATA[payments]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Luke]]></dc:creator><pubDate>Mon, 02 Nov 2026 17:54:02 GMT</pubDate><content:encoded><![CDATA[<p>AIRap turns photos into short rap and singing videos based on viral moments. When I designed it, I made two decisions that shaped most of the backend: there is no subscription, and there is no required account. You add your photos, pay $6.99 on the template page, and get the video. (I'm the developer of AIRap.)</p>
<p>Removing the account sounds like it removes work. It actually moves the work into three places.</p>
<h2>1. Identity without sign-up</h2>
<p>Every visitor still needs an identity, because a paid video takes minutes to render and the buyer has to be able to come back to it. So the browser gets an anonymous identity on first visit, and jobs and credits attach to it. Signing in, with Google or an emailed code, is optional and only exists to carry that history to another device.</p>
<h2>2. Recovery</h2>
<p>Anonymous identities get lost: a cleared browser, a new phone, a purchase made on a laptop and checked on mobile. The fix  is to let a buyer reclaim purchases using things they already have, namely the email they paid with and the receipt number from the payment provider's email. Signing in with the paying email does the same automatically.</p>
<h2>3. Honest failure handling</h2>
<p>AI renders fail sometimes. In a pay-per-video model, a failure that costs the buyer money is the fastest way to lose trust. The rules are simple to state:</p>
<ul>
<li>If a photo cannot be used, nothing is charged.</li>
<li>If a render fails on our side, what was paid comes back as credits, so the video can be made again.</li>
<li>Unused credits can be refunded within 14 days.</li>
</ul>
<p>Credits also make the credit packs simple: 52, 140 or 300 credits, every current template costs 16, and pack credits never expire.</p>
<h2>What it costs</h2>
<p>The checkout has more edge cases than a subscription, and analytics are lumpier. But the conversion argument is strong: the urge to make a trend video is short, and every screen between "I want this" and "here are my photos" loses people.</p>
<p>AIRap is at <a href="https://airap.tv">airap.tv</a>; pricing details are <a href="https://airap.tv/pricing">here</a>. It is an independent project and not affiliated with the artists or the original videos.</p>
]]></content:encoded></item><item><title><![CDATA[Plan first, render second: how a photo-to-trend template works]]></title><description><![CDATA[Splitting AI generation into a planning step and a rendering step]]></description><link>https://luke-builds.hashnode.dev/plan-first-render-second-how-a-photo-to-trend-template-works</link><guid isPermaLink="true">https://luke-builds.hashnode.dev/plan-first-render-second-how-a-photo-to-trend-template-works</guid><category><![CDATA[generative ai]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Product Engineering]]></category><dc:creator><![CDATA[Luke]]></dc:creator><pubDate>Mon, 02 Nov 2026 16:35:52 GMT</pubDate><content:encoded><![CDATA[<p>YayClip turns a photo into a version of a viral video or image trend. From the outside it looks like one step: upload, wait, download. Inside, each template is a small pipeline, and the most useful design decision was splitting it into a planning step and a rendering step. (I'm the developer of YayClip.)</p>
<h2>The problem with one-shot generation</h2>
<p>If you hand a video model a photo and a description of the trend, it will produce something. But trends are specific: a ramp at night, a hook-shaped launch, a flip, a landing in smoke. One-shot generation drifts on exactly those details, and it drifts differently for a hatchback, a motorcycle and a dog.</p>
<h2>Step 1: plan</h2>
<p>A language model with vision (Claude, in our case) looks at the uploaded photo and writes a plan for that specific input: what the subject is, what must be preserved (colour, dents, roof rack and stickers on a car; markings on a pet), and how the fixed scene should adapt. For a two-person format, it also decides who goes where.</p>
<h2>Step 2: render</h2>
<p>An image or video model takes the plan plus the photo and renders the result. Because the plan has already resolved the ambiguity, the render instructions can be strict about the parts that make the trend recognisable.</p>
<h2>What the split buys you</h2>
<ul>
<li><strong>Robustness to odd inputs.</strong> The <a href="https://yayclip.com/flying-car-ai">Flying Car</a> template accepts cars, bikes, pets and people; the plan step is what makes "anything goes up the ramp" work.</li>
<li><strong>Honest failure handling.</strong> Credits are reserved when a job starts and returned automatically if no result comes back, so nobody pays for a failure.</li>
<li><strong>Privacy by default.</strong> Photos and results are private to the account and purged after 30 days by a scheduled job.</li>
</ul>
<h2>What it costs</h2>
<p>Latency. Planning adds a little time, and video renders take minutes. For a 15-second clip that has to match a trend beat for beat, a few minutes of waiting is the price of a result that actually looks like the trend.</p>
<p>The other templates are at <a href="https://yayclip.com">yayclip.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[A root page is a tree, not a list: designing word families for vocabulary learners]]></title><description><![CDATA[Most vocabulary tools show a root as a flat list: gen — birth, produce, kind followed by forty words in alphabetical order. It looks complete, and it…]]></description><link>https://luke-builds.hashnode.dev/a-root-page-is-a-tree-not-a-list-designing-word-families-for-vocabulary-learners</link><guid isPermaLink="true">https://luke-builds.hashnode.dev/a-root-page-is-a-tree-not-a-list-designing-word-families-for-vocabulary-learners</guid><category><![CDATA[Language Learning]]></category><dc:creator><![CDATA[Luke]]></dc:creator><pubDate>Sun, 01 Nov 2026 19:38:53 GMT</pubDate><content:encoded><![CDATA[<p>Most vocabulary tools show a root as a flat list: <em>gen — birth, produce, kind</em> followed by forty words in alphabetical order. It looks complete, and it teaches almost nothing. A learner scrolling from <em>benign</em> to <em>engine</em> to <em>genre</em> cannot see why those words belong together, so the list becomes another thing to memorise.</p>
<p>When we built root pages for Wordiyo we tried a different shape: group the family by <strong>what comes before the root</strong>.</p>
<h2>Branch by prefix</h2>
<p>For <em>gen</em> that gives branches like these:</p>
<ul>
<li><strong>in-</strong>: ingenious, ingenuity, ingenuous, disingenuous</li>
<li><strong>con-</strong>: congenital, congenial</li>
<li><strong>de-</strong>: degenerate, degeneration</li>
<li><strong>re-</strong>: regenerate, regeneration</li>
<li><strong>mal-</strong> / <strong>ben-</strong>: malignant, benign</li>
<li><strong>pre-</strong>: pregnant, pregnancy</li>
</ul>
<p>Each branch is a small, learnable unit. <em>De-</em> plus "birth, kind" gives <em>degenerate</em>, falling away from your kind. <em>Re-</em> gives <em>regenerate</em>, being produced again. The prefix carries a direction, and the learner gets to rebuild the meaning instead of reading it.</p>
<h2>Show variants, or the tree breaks</h2>
<p>The same root hides under different spellings. <em>Gen</em> also appears as <em>gene</em> and, squeezed, as bare <em>gn</em>: <em>pregnant</em>, <em>benign</em>, <em>malignant</em>, <em>cognate</em>. If a page only matches the string "gen", half the family goes missing and the learner concludes the root idea is unreliable. Listing variants explicitly at the top of the page fixes that.</p>
<h2>Put the traps on the page</h2>
<p>A tree also makes false friends visible. <em>Ingenious</em> (clever) and <em>ingenuous</em> (naive, frank) sit on the same branch and come from the same "inborn" idea, yet they drifted to almost opposite reputations. Placing them side by side, with a one-line note, does more than any mnemonic.</p>
<p>The same goes for words that only <em>look</em> like members. On our <em>port</em> page, <em>opportunity</em> is flagged as coming from the neighbouring Latin word <em>portus</em> (harbour), not from <em>portare</em> (to carry): <em>ob portum</em>, a wind blowing you toward the harbour.</p>
<h2>Free layer, deep layer</h2>
<p>A tree is useful even at its shallowest: the word, its pronunciation, a definition and the parts it is made of. That layer is free on every word. The deeper layer (what each part means, the story of the root, memory tips) sits on top for learners who want it.</p>
<h2>The takeaway</h2>
<p>If you are building anything that teaches words, group by structure, not by alphabet. The structure is the lesson.</p>
<p>You can see the full <em>gen</em> tree here: <a href="https://wordiyo.com/root/gen">https://wordiyo.com/root/gen</a></p>
<hr />
<p><em>Disclosure: I'm part of the team behind Wordiyo.</em></p>
]]></content:encoded></item><item><title><![CDATA["300 DPI" is nine bytes: what DPI converters actually change]]></title><description><![CDATA[Why most DPI tools leave your print exactly as blurry as before, and what to do instead]]></description><link>https://luke-builds.hashnode.dev/300-dpi-is-nine-bytes-what-dpi-converters-actually-change</link><guid isPermaLink="true">https://luke-builds.hashnode.dev/300-dpi-is-nine-bytes-what-dpi-converters-actually-change</guid><category><![CDATA[webdev]]></category><category><![CDATA[image processing]]></category><category><![CDATA[printing]]></category><category><![CDATA[Photography]]></category><dc:creator><![CDATA[Luke]]></dc:creator><pubDate>Sun, 01 Nov 2026 17:08:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7714920c7f2c5cf6fb8b0/5ae645eb-224f-46a1-b3c1-b7a85d0b6964.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A print shop rejects your file: "minimum 300 DPI." You search for a converter, drop the image in, and download a file that now says 300 DPI. The shop accepts it. The print comes back exactly as soft as before.</p>
<p>That is not bad luck. It is what most DPI converters are built to do.</p>
<h2>DPI is a header field, not a property of the picture</h2>
<p>A raster image is a grid of pixels. DPI (strictly PPI) is a single number stored in the file header that tells layout software how many of those pixels to place in each printed inch. In a PNG it lives in the <code>pHYs</code> chunk; in a JPEG, in the JFIF or EXIF header. Changing it is a nine-byte edit, and it can be done without decoding the image at all.</p>
<p>So a 1200 × 1904 scan "converted" from 72 to 300 DPI is still 1200 × 1904 pixels. Your layout app simply prints it smaller. If you place it at the original size, the RIP has to stretch it anyway, with whatever scaler it happens to ship.</p>
<p>The free check takes five seconds: <strong>note the pixel dimensions before and after.</strong> If they did not change, nothing that matters changed.</p>
<h2>What actually helps: resample, then tag</h2>
<p>To get more printable detail you need more pixels, which means resampling:</p>
<ol>
<li>Work out the pixels your print size needs. An 8 × 10 in print at 300 DPI needs 2400 × 3000 px.</li>
<li>Pick the smallest scale (2×, 3×, 4×) that clears that number.</li>
<li>Resample with a good filter, <em>then</em> write the DPI value so layout software places it at the right physical size.</li>
</ol>
<p>The filter matters more than people expect, and there is no single winner. In our measurements over the same set of documents, a classical resample (Lanczos-3 plus a sharpen clamped to each pixel's neighbourhood, so it cannot halo) was the best free option on crisp scans. On soft scans the ranking inverted: a small restoration network beat it on every one of ten soft test scans while finishing near the bottom on crisp ones. "Which upscaler is best" only has an answer once you say what the source looks like.</p>
<h2>Honest limits</h2>
<p>Resampling reconstructs, it does not recover. Halftone dots and dense engraving lines that had already merged in the scan stay merged. A larger file is a cleaner version of the same information, which is usually what a print job needs, but it is not a better original.</p>
<h2>Where to try it</h2>
<p>I built a free browser tool for exactly this: it resamples the pixels on your own GPU, then writes a real 300 (or 72/150/350/600) DPI value into the PNG. Nothing is uploaded and there is no sign-up for that part: <a href="https://freeupscaler.net/change-dpi">FreeUpscaler — Change Image DPI</a>.</p>
<p><em>Disclosure: I'm the developer of FreeUpscaler.</em></p>
]]></content:encoded></item></channel></rss>