<?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>Forem: Cassi Lup 🐺</title>
    <description>The latest articles on Forem by Cassi Lup 🐺 (@cassilup).</description>
    <link>https://forem.com/cassilup</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F31983%2F1dd85d61-11ef-4318-a0cd-4bfebe0f6869.jpg</url>
      <title>Forem: Cassi Lup 🐺</title>
      <link>https://forem.com/cassilup</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cassilup"/>
    <language>en</language>
    <item>
      <title>How I Built Browser-Based File Tools That Never Upload Your Data</title>
      <dc:creator>Cassi Lup 🐺</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:15:15 +0000</pubDate>
      <link>https://forem.com/cassilup/how-i-built-browser-based-file-tools-that-never-upload-your-data-1oe4</link>
      <guid>https://forem.com/cassilup/how-i-built-browser-based-file-tools-that-never-upload-your-data-1oe4</guid>
      <description>&lt;p&gt;Every time I needed to compress an image or merge a PDF, I'd end up on some random website, uploading sensitive files to a server I didn't control. It always felt wrong.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://filewash.app" rel="noopener noreferrer"&gt;FileWash&lt;/a&gt; — a suite of free file tools where &lt;strong&gt;everything runs in your browser&lt;/strong&gt;. Your files never leave your device.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Tools like TinyPNG, iLovePDF, and remove.bg work great. But they all share the same architecture: upload your file to a server, process it there, download the result.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your files pass through someone else's server&lt;/li&gt;
&lt;li&gt;You're trusting their privacy policy (which you didn't read)&lt;/li&gt;
&lt;li&gt;There are usually file size limits, watermarks, or account requirements&lt;/li&gt;
&lt;li&gt;It doesn't work offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For personal photos with EXIF location data? For company documents? For legal PDFs? The upload model feels like a compromise we shouldn't have to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Client-Side Everything
&lt;/h2&gt;

&lt;p&gt;FileWash processes files entirely in the browser using JavaScript APIs. Here's how each tool works under the hood:&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Compression
&lt;/h3&gt;

&lt;p&gt;Uses the Canvas API with quality adjustment. Drop a JPEG in, and the browser re-encodes it at a lower quality setting:&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;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Quality 0.0-1.0 controls compression&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;browser-image-compression&lt;/code&gt; library wraps this with smart defaults — resizing, WebWorker support, and format detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Format Conversion
&lt;/h3&gt;

&lt;p&gt;HEIC to JPG uses the &lt;code&gt;heic2any&lt;/code&gt; library which decodes Apple's HEIC format in JavaScript. SVG to PNG uses canvas rasterization. WebP/PNG/JPG conversions use the Canvas API's built-in codec support.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF Processing
&lt;/h3&gt;

&lt;p&gt;PDF merge, split, and compress use &lt;code&gt;pdf-lib&lt;/code&gt; (for manipulation) and &lt;code&gt;pdfjs-dist&lt;/code&gt; (for rendering). These are the same libraries that power Firefox's PDF viewer — battle-tested and fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Removal
&lt;/h3&gt;

&lt;p&gt;This was the most impressive one to get working client-side. It uses &lt;code&gt;@imgly/background-removal&lt;/code&gt;, which loads an ONNX neural network model via WebAssembly. The model runs inference directly in the browser — no API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  QR Code Generation
&lt;/h3&gt;

&lt;p&gt;Uses the &lt;code&gt;qrcode&lt;/code&gt; library with error correction level H (30% redundancy), which means you can embed a logo in the center and the code still scans.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React 19&lt;/strong&gt; + &lt;strong&gt;TypeScript&lt;/strong&gt; + &lt;strong&gt;Vite&lt;/strong&gt; — fast builds, great DX&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — styling without the CSS bloat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-rendered to static HTML&lt;/strong&gt; — 464 pages across 16 languages, built at compile time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy-loaded tool pages&lt;/strong&gt; — the homepage bundle is 275KB, tool-specific code loads on demand&lt;/li&gt;
&lt;li&gt;All deployed as static files behind &lt;strong&gt;Cloudflare&lt;/strong&gt; CDN&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance: The Lazy Loading Story
&lt;/h2&gt;

&lt;p&gt;The first version had a 1.4MB main bundle — every tool's dependencies loaded on every page. The homepage took 8+ seconds to become interactive on slow connections (India, mobile).&lt;/p&gt;

&lt;p&gt;The fix was simple: &lt;code&gt;React.lazy()&lt;/code&gt; for every tool page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: everything in one bundle&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ImageCompress&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/ImageCompress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PdfMerge&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/PdfMerge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BackgroundRemover&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/BackgroundRemover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// After: each tool loads on demand&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ImageCompress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/ImageCompress&lt;/span&gt;&lt;span class="dl"&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;PdfMerge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/PdfMerge&lt;/span&gt;&lt;span class="dl"&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;BackgroundRemover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/BackgroundRemover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;strong&gt;1.4MB to 275KB main bundle&lt;/strong&gt; (80% reduction). The heic2any library (1.3MB) now only loads when someone visits the HEIC converter. The ONNX runtime (381KB) only loads for background removal.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO for a Static SPA
&lt;/h2&gt;

&lt;p&gt;FileWash supports 16 languages with 29 tool pages each = 464 URLs. All pre-rendered to static HTML at build time.&lt;/p&gt;

&lt;p&gt;Each page has unique title tags, meta descriptions, FAQ schema markup, and breadcrumb structured data. The sitemap is generated automatically from the route definitions.&lt;/p&gt;

&lt;p&gt;The pre-rendering script launches a local server, visits each route with a headless browser, and saves the rendered HTML. This gives Google fully crawlable content without needing SSR infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy as a Feature
&lt;/h2&gt;

&lt;p&gt;The key insight: &lt;strong&gt;privacy isn't a restriction, it's a feature&lt;/strong&gt;. When files never leave the device:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No server costs for file processing&lt;/li&gt;
&lt;li&gt;No storage or bandwidth bills&lt;/li&gt;
&lt;li&gt;No GDPR compliance complexity&lt;/li&gt;
&lt;li&gt;No data breach risk&lt;/li&gt;
&lt;li&gt;Works offline (after first load)&lt;/li&gt;
&lt;li&gt;No file size limits&lt;/li&gt;
&lt;li&gt;No account required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only analytics is Cloudflare's cookieless Web Analytics beacon — no cookies, no consent banner needed.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client-side processing is surprisingly capable.&lt;/strong&gt; Canvas API handles most image operations. WebAssembly unlocks AI models in the browser. PDF libraries are mature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bundle size is your LCP.&lt;/strong&gt; Lazy loading isn't optional for tool-heavy SPAs. One &lt;code&gt;React.lazy()&lt;/code&gt; change cut our LCP P90 from 8 seconds to under 2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pre-rendering beats SSR for static content.&lt;/strong&gt; No server runtime, no edge functions, just HTML files on a CDN. Simpler, cheaper, faster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy sells itself.&lt;/strong&gt; The "100% private — files never leave your browser" badge in the header isn't just marketing. Users mention it in every positive interaction.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://filewash.app" rel="noopener noreferrer"&gt;FileWash&lt;/a&gt; is free, no account required. Compress images, merge PDFs, convert formats, remove backgrounds, generate QR codes with custom logos, strip metadata — all in your browser.&lt;/p&gt;

&lt;p&gt;Available in 16 languages: English, Spanish, Chinese, German, French, Japanese, Portuguese, Korean, Italian, Russian, Dutch, Polish, Turkish, Arabic, Hindi, Romanian.&lt;/p&gt;

&lt;p&gt;I'd love feedback — especially on compression quality, background removal accuracy, and any tools you'd want to see added.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with React, TypeScript, Vite, and Tailwind CSS. No servers were harmed in the processing of your files.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How Tariffs Actually Hit Your Wallet: Building a Calculator with Real Data</title>
      <dc:creator>Cassi Lup 🐺</dc:creator>
      <pubDate>Fri, 03 Apr 2026 09:10:42 +0000</pubDate>
      <link>https://forem.com/cassilup/how-tariffs-actually-hit-your-wallet-building-a-calculator-with-real-data-ca2</link>
      <guid>https://forem.com/cassilup/how-tariffs-actually-hit-your-wallet-building-a-calculator-with-real-data-ca2</guid>
      <description>&lt;p&gt;With all the noise around tariffs in 2025-2026, I wanted to answer a simple question: &lt;strong&gt;how much do tariffs actually cost my household?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turns out, the data exists. The &lt;a href="https://budgetlab.yale.edu/" rel="noopener noreferrer"&gt;Yale Budget Lab&lt;/a&gt; has published detailed research on tariff impacts broken down by income tier. So I built a calculator that makes this data accessible to anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it here:&lt;/strong&gt; &lt;a href="https://fincruncher.com/calculators/tariff-impact-calculator/" rel="noopener noreferrer"&gt;Tariff Impact Calculator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Model
&lt;/h2&gt;

&lt;p&gt;The core insight from the research is that tariff impact isn't linear — it hits different income tiers and spending categories differently. Here's the simplified model:&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;const&lt;/span&gt; &lt;span class="nx"&gt;TARIFF_TIERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;baseImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Under $50K&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;75000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;baseImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$50K - $75K&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;baseImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$75K - $100K&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;baseImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$100K - $150K&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;baseImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$150K+&lt;/span&gt;&lt;span class="dl"&gt;"&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;A household earning $75K/year can expect roughly &lt;strong&gt;$2,400/year in higher prices&lt;/strong&gt; from tariffs. That's $200/month that silently disappears.&lt;/p&gt;

&lt;p&gt;But the real nuance is in &lt;em&gt;where&lt;/em&gt; that money goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking It Down by Category
&lt;/h2&gt;

&lt;p&gt;Not all spending categories are equally affected. Electronics have heavy tariff exposure (most are imported), while services have almost none. Here's how I weighted it:&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;const&lt;/span&gt; &lt;span class="nx"&gt;CATEGORY_WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;groceries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Groceries &amp;amp; Food&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;electronics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Electronics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;clothing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Clothing &amp;amp; Apparel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Automotive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;homeGoods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home Goods&lt;/span&gt;&lt;span class="dl"&gt;"&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;Electronics take the biggest hit at 28% of the total impact. If you're buying a new phone, laptop, or TV this year, you're feeling it more than average.&lt;/p&gt;

&lt;p&gt;The calculator lets users adjust their spending per category with sliders — someone who rarely buys electronics but spends heavily on groceries will see a different (lower) number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interpolation Problem
&lt;/h2&gt;

&lt;p&gt;One thing that bugged me about simple tier-based calculators: if you earn $49,999 you get one number, and at $50,001 you get a completely different one. That's not how economics works.&lt;/p&gt;

&lt;p&gt;So I added smooth interpolation between tiers:&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;const&lt;/span&gt; &lt;span class="nx"&gt;tierIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TARIFF_TIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tier&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;tierIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;prevTier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TARIFF_TIERS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tierIndex&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prevMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prevTier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&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;currMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;200000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&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;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;income&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prevMax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prevMax&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;baseImpact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prevTier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseImpact&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
    &lt;span class="nx"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseImpact&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prevTier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseImpact&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;This gives a smooth curve instead of hard jumps between tiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: The INP Problem
&lt;/h2&gt;

&lt;p&gt;After launching, Cloudflare Web Analytics flagged a &lt;strong&gt;504ms Interaction to Next Paint (INP)&lt;/strong&gt; on this calculator — 100% Poor. Every keystroke in the income input triggered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tier interpolation math&lt;/li&gt;
&lt;li&gt;Category weighting across 5 categories&lt;/li&gt;
&lt;li&gt;Category breakdown array generation&lt;/li&gt;
&lt;li&gt;PDF data reconstruction via &lt;code&gt;useMemo&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All synchronous, all blocking the paint.&lt;/p&gt;

&lt;p&gt;The fix was simple — React 19's &lt;code&gt;useTransition&lt;/code&gt;:&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;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTransition&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Before (blocking):&lt;/span&gt;
&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIncome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;

&lt;span class="c1"&gt;// After (non-blocking):&lt;/span&gt;
&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;startTransition&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIncome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&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 browser now paints the input response immediately while the expensive calculations happen in the background. LCP went to 100% Good.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; with static export (&lt;code&gt;output: "export"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; for styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; for CDN + Web Analytics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jsPDF&lt;/strong&gt; for downloadable PDF reports&lt;/li&gt;
&lt;li&gt;No database, no auth, no tracking cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire site is static HTML deployed via rsync. No server-side logic needed — all calculations run client-side.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real data beats estimates.&lt;/strong&gt; Using Yale Budget Lab numbers gives credibility that "I made up some percentages" doesn't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smooth interpolation matters.&lt;/strong&gt; Users notice when their result jumps $700 from a $1 income change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;INP is a real ranking signal.&lt;/strong&gt; Google uses Core Web Vitals for rankings. A 504ms INP on your most-visited page is actively hurting your SEO.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;useTransition&lt;/code&gt; is underused.&lt;/strong&gt; For any calculator/form with expensive derived state, it's a one-line fix that dramatically improves responsiveness.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloudflare Web Analytics &amp;gt; Google Analytics&lt;/strong&gt; for understanding real traffic. GA showed 65 users in a week while Cloudflare HTTP traffic showed 944 "visitors" — most of which were bots. CF Web Analytics (the JS beacon, not the HTTP dashboard) gave the honest number: 20 real humans.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;The calculator is part of &lt;a href="https://fincruncher.com" rel="noopener noreferrer"&gt;FinCruncher&lt;/a&gt;, a free suite of 31 financial calculators. No sign-up, no ads, no tracking — just crunch your numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fincruncher.com/calculators/tariff-impact-calculator/" rel="noopener noreferrer"&gt;Try the Tariff Impact Calculator&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building financial tools or have questions about the data model, drop a comment. Happy to share more details.&lt;/p&gt;

</description>
      <category>finance</category>
      <category>nextjs</category>
      <category>tailwindcss</category>
      <category>jspdf</category>
    </item>
    <item>
      <title>I Built 31 Free Financial Calculators with Embeddable Widgets — Here's How</title>
      <dc:creator>Cassi Lup 🐺</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:59:03 +0000</pubDate>
      <link>https://forem.com/cassilup/i-built-31-free-financial-calculators-with-embeddable-widgets-heres-how-45mb</link>
      <guid>https://forem.com/cassilup/i-built-31-free-financial-calculators-with-embeddable-widgets-heres-how-45mb</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://fincruncher.com" rel="noopener noreferrer"&gt;FinCruncher&lt;/a&gt; — a collection of 31 free financial calculators. No accounts, no tracking, no ads. Just calculators.&lt;/p&gt;

&lt;p&gt;I wanted to share the technical approach, especially the &lt;strong&gt;embeddable widget system&lt;/strong&gt; that lets anyone add these calculators to their own site with a single line of HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; with static export (&lt;code&gt;output: 'export'&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; for styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jsPDF&lt;/strong&gt; for client-side PDF generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; for IP-based geolocation&lt;/li&gt;
&lt;li&gt;Hosted on a Linode VPS behind Cloudflare CDN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs client-side. Your financial data never leaves your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Calculators
&lt;/h2&gt;

&lt;p&gt;31 calculators covering mortgage, compound interest, credit card payoff, retirement savings, debt payoff, ROI, income tax, car insurance, and more. Three of them (mortgage, income tax, car insurance) have &lt;strong&gt;state-specific versions for all 50 US states + DC&lt;/strong&gt; — that's 153 additional pages with localized data.&lt;/p&gt;

&lt;p&gt;Each calculator includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactive visualizations (charts, amortization tables)&lt;/li&gt;
&lt;li&gt;PDF download of results (Letter/A4 format toggle)&lt;/li&gt;
&lt;li&gt;Mobile-friendly responsive design&lt;/li&gt;
&lt;li&gt;Structured data for SEO&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Embeddable Widget System
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of. Any calculator can be embedded on any website with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://fincruncher.com/embed/tariff-impact-calculator/"&lt;/span&gt; 
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"700"&lt;/span&gt; &lt;span class="na"&gt;frameborder=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border:none;border-radius:12px;max-width:680px;"&lt;/span&gt; 
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How it works architecturally
&lt;/h3&gt;

&lt;p&gt;Next.js route groups made this clean. The main site lives under &lt;code&gt;(main)/&lt;/code&gt; and the widgets live under &lt;code&gt;(widgets)/&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;src/app/
├── (main)/           ← Full site with header, footer, analytics
│   ├── layout.tsx    ← robots: index, follow
│   └── calculators/
│       └── mortgage-calculator/
└── (widgets)/        ← Minimal wrapper, no chrome
    ├── layout.tsx    ← robots: noindex (don't index iframes)
    └── embed/
        └── mortgage-calculator/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;(widgets)&lt;/code&gt; layout strips everything — no header, no footer, no analytics. Just the calculator component with a small "Powered by FinCruncher" link at the bottom. Both route groups share the same calculator components, so there's zero code duplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Powered by" backlink
&lt;/h3&gt;

&lt;p&gt;Every embed includes a subtle footer link back to FinCruncher. This is the growth flywheel — every site that embeds a calculator gives us a permanent backlink. Calculator.net grew significantly through this exact strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tariff Impact Calculator
&lt;/h2&gt;

&lt;p&gt;The most timely one: it estimates how 2025-2026 US tariffs affect household budgets, using &lt;a href="https://budgetlab.yale.edu/" rel="noopener noreferrer"&gt;Yale Budget Lab&lt;/a&gt; data. You can adjust by income level and spending category (groceries, electronics, automotive, etc.).&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://fincruncher.com/calculators/tariff-impact-calculator/" rel="noopener noreferrer"&gt;fincruncher.com/calculators/tariff-impact-calculator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  State Detection with Cloudflare Workers
&lt;/h2&gt;

&lt;p&gt;For the state-specific calculators, I built a tiny Cloudflare Worker that returns the visitor's US state from their IP:&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;cf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;StateSelector&lt;/code&gt; component calls this on mount and auto-selects the user's state. Simple, fast, no third-party API needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to embed a calculator?
&lt;/h2&gt;

&lt;p&gt;All embed codes are at &lt;a href="https://fincruncher.com/embed/" rel="noopener noreferrer"&gt;fincruncher.com/embed&lt;/a&gt;. The source code for the embed examples is on GitHub: &lt;a href="https://github.com/cassilup/fincruncher-embeds" rel="noopener noreferrer"&gt;github.com/cassilup/fincruncher-embeds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're building a personal finance blog, real estate site, or anything where a mortgage/compound interest/tariff calculator would be useful — feel free to embed them. No API key, no signup, completely free.&lt;/p&gt;




&lt;p&gt;Happy to answer questions about the architecture, the static export approach, or the embed system!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
