<?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: MachineConvert</title>
    <description>The latest articles on DEV Community by MachineConvert (@machineconvert).</description>
    <link>https://dev.to/machineconvert</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%2F4203574%2F153b5c47-f3d2-431d-b87b-eec528fab8ee.png</url>
      <title>DEV Community: MachineConvert</title>
      <link>https://dev.to/machineconvert</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/machineconvert"/>
    <language>en</language>
    <item>
      <title>Building MachineConvert: a static site where every number has to show its source</title>
      <dc:creator>MachineConvert</dc:creator>
      <pubDate>Sat, 24 Oct 2026 00:30:18 +0000</pubDate>
      <link>https://dev.to/machineconvert/building-machineconvert-a-static-site-where-every-number-has-to-show-its-source-3h9i</link>
      <guid>https://dev.to/machineconvert/building-machineconvert-a-static-site-where-every-number-has-to-show-its-source-3h9i</guid>
      <description>&lt;p&gt;If you train in more than one gym, you know the problem: 60 kg on one brand's leg press is not 60 kg on another's. Levers, cams and pulleys change the force you actually feel, so the number on the stack doesn't carry over. I built &lt;a href="https://machineconvert.com" rel="noopener noreferrer"&gt;MachineConvert&lt;/a&gt; to catalog strength machines across brands and to answer one question: &lt;em&gt;"I lift X on this machine, what is it on that one?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is how it's built, and the one rule that shaped almost every decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: no number without a source
&lt;/h2&gt;

&lt;p&gt;The rule is that &lt;strong&gt;every figure on the site carries a source (URL plus an exact quote), a date and a confidence level&lt;/strong&gt;. If any of those is missing, the site says "no data" instead.&lt;/p&gt;

&lt;p&gt;There are three confidence levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;measured&lt;/strong&gt;: a force gauge or dynamometer reading with photo or video proof&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;manufacturer&lt;/strong&gt;: copied from an official spec page or sheet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;estimated&lt;/strong&gt;: calculated from lever, cam or pulley ratios, with a written method and a low/high range&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One design decision followed from that. Manufacturer stack weights live on the machine record, never in the measurement data, and the comparator only uses &lt;em&gt;measured&lt;/em&gt; or &lt;em&gt;estimated&lt;/em&gt; force curves. A stack weight tells you what's on the pin, not what you feel in your hands. When the comparator does return a number, it is labeled with the &lt;strong&gt;lowest&lt;/strong&gt; confidence of the two sides, and both machines show their own badge underneath.&lt;/p&gt;

&lt;p&gt;Today the catalog has 24 brands, 50 product lines and 283 machines, with 353 sourced manufacturer values and 81 estimated points. There are &lt;strong&gt;0 measured points&lt;/strong&gt; so far, and the site says so. A comparator that says "No data yet" is better than one that makes up an answer.&lt;/p&gt;

&lt;p&gt;The badges are filled pills with dark text and a distinct icon per level (check, document, ruler), so they don't rely on color alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  A plain static site
&lt;/h2&gt;

&lt;p&gt;There's no framework and no backend for the catalog. It's a static site: JSON data, a small vanilla JS app, and one prerendered entry page per route. That's 289 pages (283 machine pages plus a few others), each with its own title, description, canonical URL and JSON-LD. Structured data never contains load figures, so search snippets can't show a number without its source. The site is hosted on Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  A synthwave grid in pure CSS
&lt;/h2&gt;

&lt;p&gt;I wanted a Miami-at-night look: sun, mountains and a perspective grid scrolling toward you, animated on phones too. My first attempt drew the grid on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, and Lighthouse made clear that was too slow. The second version is pure CSS, about 3 KB. The grid is a transformed, repeating gradient whose animation runs on the GPU, and one line of JS pauses it when the tab is hidden. With &lt;code&gt;prefers-reduced-motion&lt;/code&gt; you get a static version. The technique was inspired by Jane Ori's "to the future" CodePen. I rewrote it from scratch rather than copying it, and the credit is in the repo.&lt;/p&gt;

&lt;p&gt;The catch with a busy background is legibility. A navy layer always sits over the wallpaper, and an extra dimming layer turns on everywhere except the hero areas. A script (&lt;code&gt;check-wallpaper-contrast.js&lt;/code&gt;) measures actual pixels behind the text. The worst case is 5.58:1, comfortably above WCAG AA's 4.5:1.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;build.sh&lt;/code&gt; is the real product
&lt;/h2&gt;

&lt;p&gt;Most of the work went into checks rather than features. &lt;code&gt;./build.sh&lt;/code&gt; has to be green before anything ships, and each check gets negative tests: I break things on purpose and confirm the check fails. Some of the checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rendered figures&lt;/strong&gt;: headless Chrome visits 341 pages and routes and makes sure every number shown on screen links back to a data record with source, date and level. It found 8,747 numbers and 0 unmarked ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparator engine&lt;/strong&gt;: runs 5,518 machine pairs × 60 loads and verifies that no number appears unless both sides have a force curve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: axe-core at 1280 and 390 px across 368 page states, plus a full keyboard Tab walk. 0 violations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering&lt;/strong&gt;: 30 page types at 390 and 1280 px, checking for no horizontal scroll, no clipped or overlapping text, and touch targets of at least 24 px on mobile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal links&lt;/strong&gt;: a crawler follows every route, asset, CSS &lt;code&gt;url()&lt;/code&gt; and anchor. 0 broken out of 43,527 links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source links&lt;/strong&gt;: all 360 unique source URLs in the data are fetched and the reports are dated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance budget&lt;/strong&gt;: guards against regressions, like a removed preload or a non-deferred script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That pays off in Lighthouse. Mobile scores on the live site sit at 96–100 for performance and 100 for accessibility. Before I added font/JSON preloading and &lt;code&gt;defer&lt;/code&gt;, performance was in the low 80s.&lt;/p&gt;

&lt;p&gt;One lesson: checks need to know where they run. A coverage check read a local &lt;code&gt;proofs/&lt;/code&gt; folder that isn't part of the deploy bundle, and it broke the Vercel build. Now checks that need Chrome or local proofs print &lt;code&gt;SKIPPED&lt;/code&gt; in the cloud and run fully on my machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community measurements, moderated
&lt;/h2&gt;

&lt;p&gt;The only way to get &lt;em&gt;measured&lt;/em&gt; data is from people with a force gauge in a gym. There's a "Prepare a measurement" form that builds an email instead of posting anywhere. The form and the review tool share one rules file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;em&gt;measured&lt;/em&gt; entry requires an https link to proof&lt;/li&gt;
&lt;li&gt;an &lt;em&gt;estimated&lt;/em&gt; entry requires a method (at least 20 characters) and low &amp;lt; value &amp;lt; high&lt;/li&gt;
&lt;li&gt;aggregated "it feels heavier" opinions are refused for now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Submissions go into a local queue (pending → accepted or rejected, final), handled with a small CLI. The queue is never deployed. Writing the moderation check found a real crash in the form, which is the best argument I know for writing checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  i18n, tested with a fake language
&lt;/h2&gt;

&lt;p&gt;The site is in English with French on the way. Every UI string (360 keys) goes through translation files, and a check enforces that English and French have the same keys, that no key goes unused and that placeholders match. To find hard-coded text, the whole site is rendered in a &lt;strong&gt;pseudo-locale (&lt;code&gt;en-XA&lt;/code&gt;)&lt;/strong&gt;: 28 states and 17,733 text nodes. After fixes, no English remained outside catalog data such as brand and machine names. That pass also caught a page that declared itself French while serving English. The &lt;code&gt;hreflang&lt;/code&gt; for French is only emitted once the translation is at least 95% complete, so search engines never get a half-empty locale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next step is more data, especially real measurements, plus a French version and comparison guides for common questions. If you lift in more than one gym, try the comparator on &lt;a href="https://machineconvert.com" rel="noopener noreferrer"&gt;machineconvert.com&lt;/a&gt; and tell me where it's confusing. And if you own a force gauge, I'd love your help.&lt;/p&gt;

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