<?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: Christopher Shen</title>
    <description>The latest articles on DEV Community by Christopher Shen (@christopher_shen_590282f0).</description>
    <link>https://dev.to/christopher_shen_590282f0</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%2F4100903%2Fc78b2855-04a8-4396-a31b-b3dd07b4c3fa.png</url>
      <title>DEV Community: Christopher Shen</title>
      <link>https://dev.to/christopher_shen_590282f0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christopher_shen_590282f0"/>
    <language>en</language>
    <item>
      <title>Why Tarot Birth Card Calculators Disagree — and How I Made the Math Visible</title>
      <dc:creator>Christopher Shen</dc:creator>
      <pubDate>Sun, 13 Sep 2026 01:22:46 +0000</pubDate>
      <link>https://dev.to/christopher_shen_590282f0/why-tarot-birth-card-calculators-disagree-and-how-i-made-the-math-visible-1dna</link>
      <guid>https://dev.to/christopher_shen_590282f0/why-tarot-birth-card-calculators-disagree-and-how-i-made-the-math-visible-1dna</guid>
      <description>&lt;p&gt;If you have tried more than one tarot birth card calculator, you may have noticed something confusing: the same birthday can produce different cards.&lt;/p&gt;

&lt;p&gt;That is usually not a bug. Different calculators use different reduction methods, often without showing which one they chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  One common example
&lt;/h2&gt;

&lt;p&gt;For a birthday such as March 23, 1983, two methods may look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grouped year:&lt;/strong&gt; 3 + 23 + 19 + 83 = 128, then 12 + 8 = 20, then 2 + 0 = 2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whole year:&lt;/strong&gt; 3 + 23 + 1983 = 2009, then 2 + 0 + 0 + 9 = 11, then 1 + 1 = 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first path gives &lt;strong&gt;Judgement (20) / The High Priestess (2)&lt;/strong&gt;. The second gives &lt;strong&gt;Justice (11) / The High Priestess (2)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Neither result can be understood properly unless the calculator explains its method.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method I used
&lt;/h2&gt;

&lt;p&gt;I built a small browser-based calculator using the Tarot School pair method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split the year into century and year remainder.&lt;/li&gt;
&lt;li&gt;Add month + day + century + remainder.&lt;/li&gt;
&lt;li&gt;Reduce the total according to the method's grouped rules.&lt;/li&gt;
&lt;li&gt;Preserve the special &lt;strong&gt;19 → 10 → 1&lt;/strong&gt; three-card sequence.&lt;/li&gt;
&lt;li&gt;Show every calculation step beside the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main design goal was not to claim that one tradition is universally correct. It was to make the chosen convention explicit, so people can understand why another site might give a different answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few product decisions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The birth date is calculated in the browser and is not placed in the URL.&lt;/li&gt;
&lt;li&gt;The result is framed as a reflection prompt, not a prediction or personality diagnosis.&lt;/li&gt;
&lt;li&gt;The calculator shows both the card pair and the arithmetic behind it.&lt;/li&gt;
&lt;li&gt;Each supported pair has a dedicated explanation page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try the calculator here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tarotbirthcardcalculator.online/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;Tarot Birth Card Calculator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you test it, I would especially like to know whether the visible calculation resolves the “why did another calculator give me different cards?” problem.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>What I Learned Building a Browser-Only Credit Card Test Data Generator</title>
      <dc:creator>Christopher Shen</dc:creator>
      <pubDate>Sun, 30 Aug 2026 14:10:45 +0000</pubDate>
      <link>https://dev.to/christopher_shen_590282f0/what-i-learned-building-a-browser-only-credit-card-test-data-generator-5c23</link>
      <guid>https://dev.to/christopher_shen_590282f0/what-i-learned-building-a-browser-only-credit-card-test-data-generator-5c23</guid>
      <description>&lt;h1&gt;
  
  
  What I Learned Building a Browser-Only Credit Card Test Data Generator
&lt;/h1&gt;

&lt;p&gt;Payment-form testing often begins with a surprisingly small problem: a random string of digits usually fails before the form can test anything else. The field may expect a known card-network prefix, a particular length, and a valid Luhn check digit. A tester may also need an expiry date and a three- or four-digit security code just to exercise the interface.&lt;/p&gt;

&lt;p&gt;I built a small browser-only generator to make that test data available without an account, a database, or a server-side API. The project also clarified an important boundary: a structurally valid card number is not the same thing as a card that can complete a payment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “Luhn valid” actually means
&lt;/h2&gt;

&lt;p&gt;The Luhn algorithm is a checksum. It helps software catch common input mistakes, but it does not prove that an account exists or that a transaction will be authorised.&lt;/p&gt;

&lt;p&gt;To calculate the final check digit, the generator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Starts with a card-network prefix.&lt;/li&gt;
&lt;li&gt;Adds random digits until the number is one digit short of the required length.&lt;/li&gt;
&lt;li&gt;Processes the digits from right to left, doubling every second digit and subtracting nine when the result is greater than nine.&lt;/li&gt;
&lt;li&gt;Chooses a final digit that makes the total divisible by ten.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting number can pass a basic Luhn validator. It still has no bank account, balance, issuer relationship, or payment capability behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Card networks are not all formatted the same way
&lt;/h2&gt;

&lt;p&gt;The generator uses a deliberately small set of common test profiles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Card type&lt;/th&gt;
&lt;th&gt;Test prefix used&lt;/th&gt;
&lt;th&gt;Number length&lt;/th&gt;
&lt;th&gt;Security-code length&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visa&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mastercard&lt;/td&gt;
&lt;td&gt;51–55&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;American Express&lt;/td&gt;
&lt;td&gt;34 or 37&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discover&lt;/td&gt;
&lt;td&gt;6011&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JCB&lt;/td&gt;
&lt;td&gt;3528–3589&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UnionPay&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diners Club&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These profiles are useful for front-end and validation tests, but they are not an exhaustive representation of every range used by each network. A production payment integration should be tested with the official sandbox data supplied by its payment provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why generate the data in the browser?
&lt;/h2&gt;

&lt;p&gt;The most useful privacy decision was also the simplest architectural decision: generate everything locally.&lt;/p&gt;

&lt;p&gt;The tool does not need to send generated card numbers, names, security codes, or expiry dates to a server. That removes the need for a storage layer and reduces the chance of test data appearing in logs. It also keeps the core workflow fast: select a card type, choose a quantity, and generate.&lt;/p&gt;

&lt;p&gt;The same rule applies to analytics. It is useful to know whether someone generated a batch, copied a number, exported JSON, or downloaded CSV. It is not useful—or appropriate—to send the generated values themselves. The analytics events therefore contain only an action name plus a non-sensitive card type or batch count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch output matters more than a decorative card
&lt;/h2&gt;

&lt;p&gt;A visual card can be helpful for demos, but repeated form testing needs compact output. I prioritised:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One to twenty records per batch.&lt;/li&gt;
&lt;li&gt;A copy button beside each card number.&lt;/li&gt;
&lt;li&gt;JSON copying for test fixtures and API examples.&lt;/li&gt;
&lt;li&gt;CSV download for spreadsheets and data-driven test runs.&lt;/li&gt;
&lt;li&gt;Responsive output that remains usable on both desktop and mobile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still a small tool, but those choices make it more useful than returning a single unformatted number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning one generated card into four test cases
&lt;/h2&gt;

&lt;p&gt;The latest version adds a small QA test pack. After generating a card, the tool uses the first record to create four client-side payment-form cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A structurally valid card record.&lt;/li&gt;
&lt;li&gt;The same record with a broken Luhn check digit.&lt;/li&gt;
&lt;li&gt;The same record with an expiry date in the past.&lt;/li&gt;
&lt;li&gt;The same record with a CVV or CID of the wrong length.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each case includes its purpose, input values, and expected result. A tester can copy the pack as a step-by-step Markdown plan for an AI coding tool or manual QA session, or download the same cases as structured JSON for Playwright, Cypress, or another test setup.&lt;/p&gt;

&lt;p&gt;This does not make the generator a payment simulator. It turns one synthetic record into a reusable validation checklist, while transaction approvals, declines, 3-D Secure, and other provider behaviour still belong in the payment provider's official sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this kind of generator cannot test
&lt;/h2&gt;

&lt;p&gt;Synthetic Luhn-valid data can help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field formatting and masking.&lt;/li&gt;
&lt;li&gt;Card-type detection.&lt;/li&gt;
&lt;li&gt;Required-field and length validation.&lt;/li&gt;
&lt;li&gt;Responsive checkout layouts.&lt;/li&gt;
&lt;li&gt;Demo data, documentation, and QA fixtures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It cannot test issuer approval, balances, 3-D Secure, address verification, fraud decisions, payment-provider responses, or a live transaction. Those behaviours require the official sandbox or test mode of the payment service being integrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the tool
&lt;/h2&gt;

&lt;p&gt;The finished MVP is available at &lt;a href="https://creditcardgenerator.online/" rel="noopener noreferrer"&gt;Random Credit Card Generator&lt;/a&gt;. It is free, requires no sign-in, generates the card-shaped test data locally in the browser, and can turn the first generated card into a four-case QA test pack.&lt;/p&gt;

&lt;p&gt;I am still treating it as an experiment. If you test payment forms or build QA fixtures, I would be interested in which output format or validation case would save you the most time.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>qa</category>
    </item>
  </channel>
</rss>
