一個平靜的每日檢查,關注你的身體、情緒和思維。記錄感受,在三級情緒輪上命名情感,觀察模式的出現。
AI 翻譯
A calm daily check-in for your body, your emotions, and your mind. Log sensations, name feelings on a three-level emotion wheel, and watch the patterns emerge.
健康TypeScript · JavaScriptGitHub @topsailcashew上線中營運者未確認
試用過了嗎? 點一下就好,不需要登入。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A private daily check-in for your body, your emotions and your mind.
Most mood trackers ask how you feel on a scale of one to five. Aluna asks
where you feel it, which feeling it actually is out of eighty-two, and what
your mind has been doing — then shows you what that adds up to over weeks.
Every entry is encrypted on your device before it is sent. Nobody else can read
your check-ins, including whoever runs the app.
The app is five tabs: Home, Check-in, Tools, Community,
Profile.檢視完整 README ↗A private daily check-in for your body, your emotions and your mind.
Most mood trackers ask how you feel on a scale of one to five. Aluna asks where you feel it, which feeling it actually is out of eighty-two, and what your mind has been doing — then shows you what that adds up to over weeks.
Every entry is encrypted on your device before it is sent. Nobody else can read your check-ins, including whoever runs the app.
Live at aluna-2-0.vercel.app
The app is five tabs: Home, Check-in, Tools, Community, Profile.
The quick path is the emotion wheel and a few optional context taps. The full path adds a body scan, thought patterns and a guided reflection.
Both write the same kind of entry. Skipping a step costs you nothing.
Aluna uses the Feelings Wheel: seven families, forty-one sub-categories, eighty-two specific emotions. You start broad — happy, sad, angry — and narrow until it fits.
The outer ring is where a feeling is actually chosen; the first two rings open the next one in. Precision is the point. "Uneasy" and "dreading" ask for different things, and noticing which one it is tends to be more useful than any advice about it. You can pick as many as are true, including contradictory ones, across different families.
Twenty-nine locations on a figure you can tap, each with an intensity from zero to ten and room for a note. Bodies often notice before minds do.
Saving a check-in offers a short piece of writing about what you logged — what that feeling tends to be like, and three ordinary things that can help. Written in advance and reviewed, not generated (see Why the guidance is hard-coded).
Pleasant feelings get noticing rather than fixing. Nothing here treats a good day as a problem to manage.
The Tools tab gathers the four things that are not check-ins:
Observations only appear once there is enough to draw on: ten entries overall, four either side of any comparison, and a real gap between them. A pattern found in four check-ins is noise, and saying it confidently would be worse than saying nothing.
There is no streak to protect and no notification that can interrupt you. Home counts days logged out of the last seven — a gap lowers the number and breaks nothing, because there is no run to lose. The calendar shows the gaps honestly rather than smoothing them over.
This is the part that shapes everything else.
Entry content — emotions, sensations, notes, journal — is encrypted in your browser with a key derived from your password. The key never reaches the server. A random data key does the encrypting and is stored only as two wrapped copies: one sealed by your password, one by a twelve-word recovery phrase shown once at signup.
Consequences worth knowing before you rely on it:
What is not encrypted, because the app cannot work otherwise: your email, display name, avatar, timestamps, preferences, and anything you deliberately post to Community. The privacy page says so plainly rather than implying everything is covered.
Deleting your account walks every subcollection under users/{uid} before
removing the profile and the auth user — in that order, because once the auth
user is gone nothing left behind can satisfy the security rules, and it would be
unreachable and undeletable forever.
See SECURITY.md for the threat model and the deployment checklist.
npm install
cp .env.local.example .env.local
Fill in the six NEXT_PUBLIC_FIREBASE_* values from Project settings → Your
apps → Web app. Then in the Firebase console:
firebase deploy --only firestore:rules.npm run dev
Running without those keys is safe — the app shows a setup screen naming what is missing rather than failing with an SDK error.
If you restrict the API key by website, add localhost/* alongside your
production domain or local development stops working. SECURITY.md covers this.
| Command | Does |
|---|---|
npm run dev | Development server |
npm run build | Production build |
npm run lint | ESLint |
npm run typecheck | tsc --noEmit |
firebase deploy --only firestore:rules --dry-run | Compiles the rules without deploying. Worth running before every rules change — a syntax error fails closed and locks out every user |
./scripts/vercel-env.sh | Copies the NEXT_PUBLIC_FIREBASE_* values into the linked Vercel project |
node --experimental-strip-types scripts/verify-encryption.mjs | Proves encryption end to end against the live project, on a throwaway account it then deletes |
The verification script signs in from Node, which sends no HTTP referrer. If the API key is referrer-restricted the script cannot run — that failure is the restriction working, not a bug.
src/
app/
(auth)/ sign-in, sign-up
(onboarding)/ welcome — auth gate, no app chrome
(legal)/ privacy, terms — deliberately outside the auth gate
(app)/ everything behind AuthGate:
dashboard, check-in, after, insights, history,
community, tools, breathe, ramble, journey,
journal, profile, settings, help
components/
breathe/ full-screen session, grounding
check-in/ the steps, the emotion wheel, the body map
community/ daily pulse, reflection wall
crypto/ recovery phrase, unlock screen
dashboard/ aura, hero, week wave, timelines, pattern metrics
insights/ twenty-week grid
layout/ profile/ ui/
hooks/ live Firestore views, hydration, speech, media queries
lib/
crypto/ AES-GCM cipher, key envelope, wordlist, vault
data/ emotions, body parts, breath patterns, guidance
firebase/ config, auth, entries, journal, profile, community
analytics.ts aggregation
insights.ts observations, with sample floors
wheel-geometry.ts polar helpers shared by the wheel and the donut
One document per check-in at users/{uid}/entries/{entryId}:
{ v: 1, payload: "<ciphertext>", createdAt: Timestamp }
createdAt stays in the clear because Firestore has to order and paginate on
it; a timestamp alone reveals only that the app was opened. Everything else —
including which emotion family it was — lives inside payload.
That has a cost worth naming: security rules cannot validate the shape of
something they cannot read. Ownership, append-only writes, an exact field list
and a size ceiling still hold server-side. The content limits moved to Zod on
the client, and firestore.rules says so rather than pretending otherwise.
Entries are append-only — a check-in records a moment, and editing it later would make the history a worse record. Journal notes are editable, because a piece of writing is a draft until its author says otherwise.
Every query is a single-field orderBy("createdAt"), which Firestore indexes
automatically. That is why firestore.indexes.json is empty; it is not an
oversight.
Posts on the wall carry no author id at all. Ownership lives in a separate
top-level reflectionAuthors/{reflectionId} document that only its author can
read, written in the same batch as the post and immutable afterwards — so it
cannot be forged later to seize someone else's post, and anonymity holds against
someone querying the documents directly rather than relying on the UI to hide a
field.
The resonance count can only move in step with the caller's own acknowledgement marker: +1 while creating it, −1 while removing it. One person can shift any post's count by at most their single vote.
The daily pulse counts emotion families and nothing else — never the specific
feelings, the sensations, the notes, or your name. Adding to it requires writing
your own private contribution marker for that day in the same batch. That makes
once-a-day the ordinary case rather than a hard guarantee, and
firestore.rules explains exactly where the limit stops holding instead of
overstating it.
Seven families → forty-one sub-categories → eighty-two emotions, all in
src/lib/data/emotions.ts. The wheel renders straight from that array, so
adding a feeling means editing data and nothing else.
A few labels repeat across branches — "Overwhelmed" under both Bad and Fearful.
That comes from the source wheel. Ids carry the whole lineage
(sad.hurt.embarrassed), so they stay distinct where the words do not.
All hand-rolled SVG, no charting library. HatchedBars (diagonal hatch fill,
exactly one bar solid to mark today or the peak) and the distribution donut,
which reuses ringSlicePath from the wheel's own geometry. Each chart states
what its fills mean rather than leaving the encoding to be guessed.
This is deliberate. A charting library cost 324KB of JavaScript for a single donut, built its sectors from a requestAnimationFrame sweep that never runs in a background tab, and put its tooltip out of reach of screen readers.
An API would mean sending the user's emotional state to a third party on every check-in — exactly the data the encryption exists to keep off other people's servers. The promise on the signup screen would become a lie the moment it shipped.
Fixed text also means every sentence can be reviewed before someone in a bad state reads it, which matters where a confidently wrong line lands hard.
Synthesised at runtime. Each phase turn is a singing-bowl strike — a low fundamental plus two quiet inharmonic partials, low-passed, slow attack — over a swell of pink noise whose gain and filter follow the breath. An earlier version glided between pitches and read as cartoonish.
Structural state — which step is showing, which rings exist — is CSS-driven with
no fill-mode, so the interface renders at rest if an animation never runs.
Framer Motion is used only where a missing animation costs nothing.
next-themes writes a class on <html>, and globals.css declares
@custom-variant dark (&:where(.dark, .dark *)) so Tailwind's dark: follows
that class. Without it, Tailwind v4 resolves dark: against
prefers-color-scheme, which disagrees with anyone who has chosen Light while
their OS is dark.
next.config.ts carries the static security headers and src/middleware.ts the
Content-Security-Policy, which needs a fresh nonce per request. The
Permissions-Policy denies camera, geolocation, payment, USB, magnetometer and
gyroscope outright; microphone is (self) because Ramble needs it. An empty
allowlist there denies the app its own microphone, which is not obvious from
reading it.
Next.js 16 · React 19 · TypeScript · Tailwind v4 · Firebase Auth & Firestore · Web Crypto · Web Audio · Web Speech · Framer Motion · Zod · next-themes · Sonner
Aluna is a notebook for noticing how you feel. It does not diagnose anything and is not a substitute for a doctor, a therapist or a crisis line. Support numbers are listed under Help in the app, and linked from the Journal.