A personality quiz that never asks for your date of birth. Nine numerology questions, and at the end the sign that matches your choices.
Visit the site · The screens · How it works · Running it · License
A complete astrology, numerology and tarot site, built for a tarot reader. At its center is a personality quiz: nine questions, one per numerological vibration, with twelve options each. Every option belongs to a sign, and the visitor never sees which one.
The result gives the dominant sign, the percentage for each of the twelve and a written reading. No login, no sign-up, no database: the result travels in the URL and the link can be shared.
The visitor never sees which sign an option belongs to. They pick by the text, and the tally does the rest.
9 questions × 12 options = 108 texts
│
├─ each option belongs to a sign (hidden)
├─ each question comes from a numerological vibration (1 to 9)
│
▼
dominant sign · percentages for all twelve · ruling vibration
The percentages add up to exactly 100. Rounding each part on its own doesn't balance the books: nine answers split across nine signs would give 11% × 9 = 99%. The tally uses the largest remainder method, and there's a test for it.
The options are shuffled with a fixed seed. They start out in zodiacal
order, and showing them that way would give the game away: the first option
would always be Aries. Shuffling with Math.random() would break hydration,
because server and client would draw different orders. The seed is the
question's index.
The ruling vibration comes out of the tally, not from an arbitrary number: it is the sum of the questions where the dominant sign was chosen, reduced to a single digit (45 → 9). It is the theosophical reduction actually used in numerology.
Two themes: cosmic (dark, the brand default) and lunar (light). The
choice lives in localStorage and is applied by a synchronous script in the
<head>, before the first paint, so the page doesn't flash.
![]() Home |
![]() Quiz |
![]() Result |
![]() Signs |
These aren't mockups: it's the site running.
npm run shotsopens every route in Chromium, at 1440×900 and 390×844, and saves two versions: the full page and this crop of the top, all at the same aspect ratio so the grid doesn't open up holes. Click any image to see the full page.
apps/frontend/
src/
app/ routes (App Router). Each page is a thin page.tsx
quiz/resultado/ result read from the URL, shareable
signos/[sign]/ twelve routes generated at build time
api/health/ probe for the compose healthcheck
components/
ds/ design system ported to TypeScript (24 components)
brand/ logo and brand artwork
content/ signs, vibrations, questions, site copy
features/ one folder per screen: view + css
lib/quiz/ quiz tally: pure logic, tested
styles/astro-oculta-ds/ design system, faithful copy (do not edit)
infra/ Dockerfile, compose, Caddyfile
docs/telas/ the screenshots in this README
The pages in app/ only delegate; the screen itself lives in
features/*-view.tsx. Anything interactive ("use client") is confined to
the quiz, the sign filter, the contact form and the frame. Everything else is
a server component.
| Decision | Why |
|---|---|
| No database | The result travels in the URL. Nothing is stored, so nothing needs protecting. |
| No Tailwind | The design system is plain CSS with tokens as custom properties. Tailwind would fight it. |
| Caddy, not nginx | Automatic HTTPS through built-in ACME (no certbot, no cron) and a ~40-line Caddyfile where nginx.conf would run past 100. |
| Cloudflare tunnel | The connection goes out from the machine to the edge: no public IP, no open port, no certificate to manage. |
next/font |
Fonts arrive preloaded and without layout shift, instead of Google Fonts' @import, which blocks the first paint. |
npm install
npm run dev # http://localhost:3000Quality gates, all offline:
npm run verify # typecheck + 22 tests + lintup brings the app up behind Caddy, which terminates HTTP/HTTPS.
cp infra/.env.local.example infra/.env.local # once
npm run up # http://localhost:8080
npm run logs
npm run downIn production, on a server exposed to the internet:
cp infra/.env.example infra/.env # fill in the domain
npm run up:prod # ports 80/443, automatic HTTPSNo public IP, no open port. Requires infra/.env.tunnel with the tunnel
token. The file stays out of Git.
cp infra/.env.tunnel.example infra/.env.tunnel # paste the TUNNEL_TOKEN
npm run publish # app + Caddy + tunnel
npm run publish:logsThe tunnel sits behind a Compose profile: npm run up doesn't start it.
Publishing is always an explicit command.
Containers use restart: unless-stopped, so Docker brings them back on its
own. What fails is earlier than that: while Docker Desktop is still starting
its VM, nothing is there to restart anything.
powershell -ExecutionPolicy Bypass -File infra\windows\registrar-tarefa.ps1This registers a task that runs one minute after logon, waits for the engine
to answer and brings the stack up. If it is already running, nothing happens.
The log lives in %LOCALAPPDATA%\astro-oculta-boot.log.
Also check that Docker Desktop is enabled under the Startup tab of Task Manager: registered but disabled, Windows skips it at boot and the site never comes up.
npm run logo # favicons, header icon and share image
npm run art # brand illustrations: large PNG → WebP in two sizes
npm run shots # the screenshots in this README (needs the site running)No .env goes into the repository. The .example files are the template.
| File | What for |
|---|---|
infra/.env |
Production: domain, ACME email, ports |
infra/.env.local |
Local machine: overrides the above with :80 |
infra/.env.tunnel |
Cloudflare tunnel token. It's a secret |
Contact details and social links go into the build through NEXT_PUBLIC_*.
Without them the site comes up with example values and works just the same.
That's what this repository shows, since the real phone number belongs to a
person.
- The contact form doesn't send. It validates, gives feedback and stops
there. There is no backend. The send hook is isolated and commented out in
features/contact/contact-view.tsx. - Blog without post pages. The listing uses the four texts in
content/site.ts;/blog/[slug]doesn't exist yet. - Stand-in fonts. Cormorant Garamond and Jost in place of the licensed families from the original design.
sharpwith a security advisory (npm audit): it is a transitive dependency of Next for image optimization. The suggested fix forces Next 16, a major break, and is left for a deliberate upgrade.
© 2026 NerdResolve. All rights reserved. See LICENSE.md.
The repository is public for technical review and portfolio purposes. The code may be read and studied; no license is granted to use, copy or redistribute it. The Astro Oculta brand and the original content belong to the owner.














