<?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: Madlen J.</title>
    <description>The latest articles on DEV Community by Madlen J. (@madlenjunior).</description>
    <link>https://dev.to/madlenjunior</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%2F3831914%2F273b4db9-f043-40c7-aeb0-2e8bcb0f3968.png</url>
      <title>DEV Community: Madlen J.</title>
      <link>https://dev.to/madlenjunior</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/madlenjunior"/>
    <language>en</language>
    <item>
      <title>How I organize dozens of calculators on a single WordPress site without it turning into a mess</title>
      <dc:creator>Madlen J.</dc:creator>
      <pubDate>Thu, 23 Jul 2026 11:33:20 +0000</pubDate>
      <link>https://dev.to/madlenjunior/how-i-organize-dozens-of-calculators-on-a-single-wordpress-site-without-it-turning-into-a-mess-2j0o</link>
      <guid>https://dev.to/madlenjunior/how-i-organize-dozens-of-calculators-on-a-single-wordpress-site-without-it-turning-into-a-mess-2j0o</guid>
      <description>&lt;p&gt;At some point I realized I had built a lot of calculators. Finance, health, construction, DIY, sports — different domains, different logic, different user intents. All living on the same WordPress site.&lt;/p&gt;

&lt;p&gt;The natural instinct is to throw them all on one page or dump them into a generic "tools" category. That works until it doesn't — which is around the time you hit double digits and users can't find anything.&lt;/p&gt;

&lt;p&gt;Here's how I structured &lt;a href="https://calcusense.com" rel="noopener noreferrer"&gt;CalcuSense&lt;/a&gt; to keep it manageable as it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  One custom page template per calculator
&lt;/h2&gt;

&lt;p&gt;Each calculator is its own WordPress page with a dedicated template file. No shortcodes, no plugins handling the logic — just a PHP template that loads the relevant JS file for that specific calculator.&lt;/p&gt;

&lt;p&gt;This means the mortgage calculator and the BMI calculator share zero code. They're isolated. When one breaks or needs updating, it doesn't touch anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category pages as landing hubs
&lt;/h2&gt;

&lt;p&gt;Instead of a mega menu, I built category pages — Finance, Health, Construction, DIY, Sport — each acting as a hub that lists the calculators under it. These pages also pull double duty as SEO targets for broader terms.&lt;/p&gt;

&lt;h2&gt;
  
  
  All math stays in the browser
&lt;/h2&gt;

&lt;p&gt;Every calculation happens client-side. PHP serves the page, JS runs the formula. No AJAX, no REST calls, no database reads at runtime. This keeps load times fast regardless of how many calculators are on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared CSS, isolated JS
&lt;/h2&gt;

&lt;p&gt;One global stylesheet handles layout, typography, and input styling across all calculators. Each calculator gets its own JS file for the logic. This separation means adding a new calculator is mostly just writing a new JS file and wiring it to a new template.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual benefit
&lt;/h2&gt;

&lt;p&gt;When you're maintaining 50+ calculators, the biggest risk isn't building them — it's not being able to find, fix, or update them six months later. Isolation by template and JS file means you always know exactly where the logic lives.&lt;/p&gt;

&lt;p&gt;If you're building a multi-tool site on WordPress, skip the plugin route early. A little custom template structure upfront saves a lot of pain later.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>php</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>How I built a financial calculator that works without a backend</title>
      <dc:creator>Madlen J.</dc:creator>
      <pubDate>Thu, 23 Jul 2026 11:30:09 +0000</pubDate>
      <link>https://dev.to/madlenjunior/how-i-built-a-financial-calculator-that-works-without-a-backend-4l</link>
      <guid>https://dev.to/madlenjunior/how-i-built-a-financial-calculator-that-works-without-a-backend-4l</guid>
      <description>&lt;p&gt;When I decided to add a boat loan calculator to my site, my first instinct was to reach for an API or at least a server-side calculation endpoint. Felt like the "proper" way to handle financial math.&lt;/p&gt;

&lt;p&gt;Then I stopped and asked myself: why?&lt;/p&gt;

&lt;p&gt;Every number the user needs — monthly payment, total interest, full amortization schedule, affordability limit — can be derived from a handful of inputs. There's no sensitive data involved, no database query, nothing that actually needs a round trip to a server.&lt;/p&gt;

&lt;p&gt;So I built the whole thing in vanilla JS on a WordPress custom page template. PHP serves the page, JS does all the math. No AJAX calls, no REST endpoints, no plugins handling the logic.&lt;/p&gt;

&lt;p&gt;The core of it is the standard amortization formula:&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="nx"&gt;M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="nx"&gt;n&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="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="nx"&gt;n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;P&lt;/code&gt; is the principal, &lt;code&gt;r&lt;/code&gt; is the monthly interest rate, and &lt;code&gt;n&lt;/code&gt; is the number of payments. Everything else — total interest, amortization breakdown, reverse affordability calculation — is just derivatives of this single formula running in the browser.&lt;/p&gt;

&lt;p&gt;The result is a calculator that loads instantly, works offline, and puts zero load on the server. You can see it live at &lt;a href="https://marinafinds.com" rel="noopener noreferrer"&gt;boat loan calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The only thing WordPress does here is render the template. The actual product is a static HTML shell with a JS brain.&lt;/p&gt;

&lt;p&gt;If you're building any kind of calculator tool — loan, mortgage, ROI, whatever — seriously consider whether you need a backend at all. Chances are you don't.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Building a Contact Lens Vertex Calculator: The Formula That Looks Simple Until It Isn't</title>
      <dc:creator>Madlen J.</dc:creator>
      <pubDate>Wed, 18 Mar 2026 17:33:48 +0000</pubDate>
      <link>https://dev.to/madlenjunior/building-a-contact-lens-vertex-calculator-the-formula-that-looks-simple-until-it-isnt-211i</link>
      <guid>https://dev.to/madlenjunior/building-a-contact-lens-vertex-calculator-the-formula-that-looks-simple-until-it-isnt-211i</guid>
      <description>&lt;p&gt;I needed a contact lens vertex calculator for a tool site I'm building. Seemed straightforward — there's a known formula, plug in the numbers, done.&lt;/p&gt;

&lt;p&gt;It wasn't done.&lt;/p&gt;

&lt;p&gt;Here's what I actually ran into.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Vertex Distance Is (Quick Background)
&lt;/h2&gt;

&lt;p&gt;When you wear glasses, the lens sits about 12mm in front of your eye. A contact lens sits directly on the cornea — effectively zero distance.&lt;/p&gt;

&lt;p&gt;This matters because optical power isn't linear. The same lens power behaves differently depending on how far it is from the eye. A -6.00D spectacle lens doesn't deliver -6.00D to the corneal plane. It delivers slightly less.&lt;/p&gt;

&lt;p&gt;The formula that corrects for this is called the vertex conversion formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CL Power = F / (1 - d × F)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;F&lt;/code&gt; = spectacle lens power in diopters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d&lt;/code&gt; = vertex distance in &lt;strong&gt;meters&lt;/strong&gt; (so 12mm = 0.012)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plug in -6.00 at 12mm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CL = -6.00 / (1 - 0.012 × -6.00)
CL = -6.00 / (1 + 0.072)
CL = -6.00 / 1.072
CL ≈ -5.597
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rounded to the nearest 0.25D (how contact lenses are actually manufactured): &lt;strong&gt;-5.75D&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That half diopter difference is clinically significant. At low prescriptions it barely matters. Above ±4.00D it matters a lot.&lt;/p&gt;

&lt;p&gt;So far so good. One formula, clean output. Where did it get complicated?&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: The Cylinder
&lt;/h2&gt;

&lt;p&gt;Most people with astigmatism have both a sphere and a cylinder in their prescription. My first instinct was to convert them independently.&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;The sphere and cylinder in a spectacle prescription aren't independent values — the cylinder is written as a &lt;em&gt;modification on top of the sphere&lt;/em&gt;. To convert correctly, you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert the sphere power through vertex formula → get CL sphere&lt;/li&gt;
&lt;li&gt;Add sphere + cylinder to get the combined meridian power → convert that through vertex formula&lt;/li&gt;
&lt;li&gt;Subtract the converted sphere from the converted combined → that's your CL cylinder&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In code:&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;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt;&lt;span class="p"&gt;);&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;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clSph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Only calculate cylinder if it exists&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;cyl&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cyl&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clSph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cylinder&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clCyl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;clSph&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="na"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clSph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clCyl&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;If you skip step 2 and just run the cylinder through the formula independently, you get a wrong answer. Not catastrophically wrong, but wrong enough that someone ordering contacts based on your calculator gets the wrong prescription.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: Rounding
&lt;/h2&gt;

&lt;p&gt;Contact lenses come in 0.25D increments. So every output needs to round to the nearest 0.25.&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;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&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;Simple. But here's the thing — should you round &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;after&lt;/em&gt; the cylinder calculation?&lt;/p&gt;

&lt;p&gt;If you round the sphere first, then use the rounded sphere value in the cylinder subtraction, you introduce rounding error into the cylinder. The correct approach is to do all the raw math first, then round each output independently at the very end.&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;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clSphRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clCylRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;cyl&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&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="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cyl&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;combinedRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;clCylRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;combinedRaw&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;clSphRaw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Round at the end, not during&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clSphRaw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clCylRaw&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;This is the kind of thing that doesn't show up in the formula documentation anywhere. You figure it out when you start testing edge cases and notice the outputs are slightly off.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: The Add Power
&lt;/h2&gt;

&lt;p&gt;Presbyopic patients have an "add power" in their prescription — extra magnification for near vision. It needs vertex conversion too.&lt;/p&gt;

&lt;p&gt;But add power is always positive and always refers to the near addition over the distance prescription. The vertex conversion for add is independent of sphere and cylinder — you just run it through the same formula on its own.&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;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clSphRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;clCylRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;cyl&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&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="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;clCylRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sph&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cyl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;clSphRaw&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;clAddRaw&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&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="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;clAddRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clSphRaw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clCylRaw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clAddRaw&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="nf"&gt;roundTo025&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clAddRaw&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem 4: The Edge Case Nobody Mentions
&lt;/h2&gt;

&lt;p&gt;What happens when the denominator approaches zero?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CL = F / (1 - d × F)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;d × F = 1&lt;/code&gt;, you're dividing by zero. At 12mm vertex distance, that happens when F = 83.33D — a prescription that doesn't exist in reality. So in practice you're safe.&lt;/p&gt;

&lt;p&gt;But what if someone enters a vertex distance of 100mm (0.1 meters) and a power of 10D?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 - 0.1 × 10 = 1 - 1 = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Division by zero. The calculator breaks silently or returns &lt;code&gt;Infinity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add a guard:&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;vertexConvert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vertexMm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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;denominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;power&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.001&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Invalid combination&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;denominator&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;Then handle &lt;code&gt;null&lt;/code&gt; in your UI. A vertex distance of 100mm isn't realistic for glasses, but someone will enter it. Someone always does.&lt;/p&gt;




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

&lt;p&gt;The formula is one line. The implementation is about fifty, once you handle cylinder correctly, round at the right stage, manage the add power independently, and guard against degenerate inputs.&lt;/p&gt;

&lt;p&gt;This is a pattern I keep hitting with "simple" calculators — the math is usually not the hard part. The hard part is understanding the domain well enough to know what edge cases exist, what the inputs actually mean, and where naive implementations silently produce wrong answers.&lt;/p&gt;

&lt;p&gt;For vertex conversion specifically: the cylinder interaction is the thing most online implementations get wrong. If you're building something similar, test it against known values from an optical textbook, not just against other online calculators — because a lot of them have the same bug.&lt;/p&gt;




&lt;p&gt;The working version is live at &lt;a href="https://calcusense.com/contact-lens-vertex-calculator/" rel="noopener noreferrer"&gt;contact lens calculator&lt;/a&gt; if you want to see the output behavior. The full JS is straightforward once the above is sorted — happy to share more of it in the comments if useful.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
