I recently built Meu Arcano Pessoal, a tarot calculator for Brazilian Portuguese users.
The site calculates a personal Major Arcana card from a birth date, with an optional name-based method. It also includes an annual Arcana calculator, relationship compatibility readings, and pages explaining all 22 Major Arcana cards.
The subject is tarot, but the project involved familiar web development questions:
- How can an interactive calculator work without a backend?
- How should personal inputs be handled without collecting them?
- How can one calculator grow into a useful content site?
- How do you present symbolic results without making false predictions?
- How much JavaScript does the site need?
I built it with Astro, TypeScript, browser APIs, and Cloudflare Pages.
Why I kept the calculation in the browser
The calculator asks for a birth date. One optional calculation method can also use a name.
Neither input needs to leave the device. A server would add complexity and create a privacy problem without improving the result.
The calculation follows this flow:
- The user enters a date.
- The browser validates it.
- TypeScript calculates a number between 1 and 22.
- The corresponding Arcana appears on the page.
- The input disappears when the page closes or reloads.
The site does not place birth dates or names in URLs, localStorage, cookies, analytics events, or network requests.
There is no account system, database, API route, or serverless function to maintain.
The reduction function
The site maps a number to one of the 22 Major Arcana cards. When a total is greater than 22, the calculator adds its digits again.
export function reduceToArcano(value: number): number {
let result = Math.abs(Math.trunc(value));
while (result > 22) {
result = String(result)
.split("")
.reduce((sum, digit) => sum + Number(digit), 0);
}
return result;
}
For a personal Arcana calculation, the site adds the digits in the birth date before applying the reduction:
export function calculatePersonalArcano(date: Date): number {
const digits = [
date.getDate(),
date.getMonth() + 1,
date.getFullYear(),
]
.join("")
.split("")
.map(Number);
const total = digits.reduce((sum, digit) => sum + digit, 0);
return reduceToArcano(total);
}
Input validation happens before this function runs. The form rejects impossible dates and future dates instead of letting JavaScript normalize them into another day.
For example, JavaScript can turn April 31 into a date in May. The calculator should not produce a result from a date the user never entered.
Handling tarot numbering differences
Tarot decks do not all use the same numbering.
In the Rider-Waite system, Strength is number 8 and Justice is number 11. Some traditions reverse those numbers. The Fool may appear as 0, 22, or remain unnumbered.
The site uses this sequence:
8 A Força
11 A Justiça
22 O Louco
I documented this decision on the methodology page instead of hiding it inside the calculation.
Two calculators can receive the same birth date and show different card names because they use different numbering systems. The formula may be working correctly in both cases.
Adding a name-based calculation
The optional name method uses a Pythagorean letter mapping.
Before assigning values, the browser normalizes the input:
export function normalizeName(value: string): string {
return value
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/ç/gi, "c")
.replace(/[^a-z]/gi, "")
.toUpperCase();
}
This lets names containing á, ã, ê, or ç use the same mapping as their base Latin letters.
I kept the date-only method available because asking for a full name creates friction and changes the interpretation. The interface explains what each method uses before the user chooses one.
Annual Arcana without requesting a birth year
The annual calculator needs a birth day, birth month, and target year. It does not need the user’s birth year.
Requesting the full birth date would collect an extra piece of personal information for no reason.
The calculation looks like this:
export function calculateAnnualArcano(
day: number,
month: number,
year: number
): number {
return reduceToArcano(day + month + year);
}
A person born on September 14 calculating the result for 2026 gets:
14 + 9 + 2026 = 2049
2 + 0 + 4 + 9 = 15
The result is Arcana 15.
The annual tool supports 2026 and 2027. Each result includes prompts for relationships, work, change, and one practical action. It does not claim to predict a specific event.
Compatibility without fake precision
Relationship calculators often return scores such as “87% compatible.” I did not have a defensible way to calculate that number, so this tool does not produce one.
The compatibility page calculates two individual Arcana cards and describes:
- what each person may bring to the relationship;
- differences in communication;
- what each person may need;
- likely points of friction;
- a question or exercise they can try together.
Users can select love, friendship, or work as the context.
Swapping the two birth dates changes the display order but not the shared interpretation. Equal Arcana results receive a combined explanation so the page does not repeat the same paragraph twice.
One data source for 22 pages
Each Arcana lives in a typed data object:
type Arcano = {
number: number;
name: string;
slug: string;
summary: string;
love: string;
work: string;
strengths: string[];
cautions: string[];
related: number[];
image: string;
};
Astro uses this data to generate the 22 detail pages at build time:
export function getStaticPaths() {
return arcanos.map((arcano) => ({
params: {
slug: `${arcano.number}-${arcano.slug}`,
},
props: {
arcano,
},
}));
}
The browser downloads no card database after the page loads. Search engines and visitors receive complete HTML.
The same data powers the calculator results, card directory, related-card links, metadata, and share images. Keeping those features attached to one source prevents card names and descriptions from drifting between pages.
Generating share cards with Canvas
Users can export their result as a 1080 by 1350 image. That size works well for vertical social posts.
The browser draws the card with the Canvas API:
const canvas = document.createElement("canvas");
canvas.width = 1080;
canvas.height = 1350;
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Canvas is not available");
}
context.fillStyle = "#0c0618";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#ead08b";
context.font = "48px serif";
context.fillText(result.name, 80, 1100);
The complete version also loads the Arcana image and adds a short meaning.
If the browser supports file sharing, the site uses the Web Share API. Otherwise, it downloads the PNG.
The image contains the result, but no name or birth date.
Analytics without personal inputs
I use Plausible to record a few product events:
calculo_concluido
resultado_aberto
compartilhamento
Events can include fixed categories such as the tool name, selected year, relationship mode, or sharing method.
They do not include:
- names;
- birth dates;
- Arcana numbers;
- generated interpretations;
- form values.
I want to know whether visitors finish a calculation and whether the sharing tool works. I do not need to know what they entered.
Static deployment on Cloudflare
Astro builds the site into static files. Cloudflare Pages serves those files behind the production domain.
The deployment has no runtime application server. Cloudflare handles HTTPS, caching, DNS, response headers, and the redirect from www to the root domain.
The site also includes:
- canonical URLs;
- Open Graph metadata;
-
robots.txt; -
sitemap.xml; - breadcrumb structured data;
- FAQ structured data where the questions are visible;
- an
llms.txtfile; - a privacy policy and methodology page.
Static generation does not remove the need for JavaScript. It limits JavaScript to the places where it earns its bytes: calculators, search, result interaction, and image sharing.
The part that took longer than the calculator
The formula was a small part of the project.
Writing useful interpretations for 22 cards took longer. Each page needed to answer the searches that brought someone there, including arcano 11, arcano pessoal 17, and significado do arcano 18.
The card illustrations also needed review. A polished image can still be wrong if it misses the symbols that identify a Rider-Waite card. I checked the characters, posture, objects, animals, background elements, and card-specific composition instead of treating the images as decoration.
The methodology page became another important part of the site. It explains why calculators disagree, how names are normalized, which numbering system the site uses, and what to do when an interpretation does not fit.
Those answers help users more than another paragraph filled with broad spiritual language.
What I would build next
I plan to watch which tools people use before adding more pages.
The current site covers personal, annual, and relationship calculations. A future addition should answer a recurring user question or improve an existing result. Publishing hundreds of thin combinations would increase the page count without making the site better.
You can try the project here:
The methodology and formulas are available here:
Top comments (0)