TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A dark-fantasy Life RPG: habits become quests, deeds pay XP and gold, five attributes level up. Next.js 16, SQLite/Turso, Drizzle.
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Keep your days like a grimoire. AetherQuest is a dark-fantasy life RPG: you write habits down as quests, seal them when they are done, and a character grows from it — levels, five attributes, a daily streak, gold, and a vault of collectables to spend it on.
It is a full-stack app. Every number the player sees is computed and stored on the server, so progress survives a refresh, follows you across devices, and cannot be edited into existence from the browser.
Live: aetherquest-black.vercel.app · Demo video: add link檢視完整 README ↗Keep your days like a grimoire. AetherQuest is a dark-fantasy life RPG: you write habits down as quests, seal them when they are done, and a character grows from it — levels, five attributes, a daily streak, gold, and a vault of collectables to spend it on.
It is a full-stack app. Every number the player sees is computed and stored on the server, so progress survives a refresh, follows you across devices, and cannot be edited into existence from the browser.
Live: aetherquest-black.vercel.app · Demo video: add link
Authentication
HttpOnly, SameSite=Lax, Secure (in production) cookie, valid for 30 days.Quests (full CRUD)
Progression
round(60 × level^1.35) + 40 XP — level 10 costs nearly 14× what level 1 did.Economy — the Vault
History — the Codex of Deeds
Feel
| Difficulty | Base XP | Base gold |
|---|---|---|
| I · Trivial | 10 | 4 |
| II · Easy | 25 | 10 |
| III · Standard | 50 | 22 |
| IV · Hard | 100 | 50 |
| V · Epic | 200 | 110 |
Award = base × streak multiplier, where the multiplier is 1 + min(streak, 30) × 0.02. The streak used is the one this completion produces, so the first deed of a new streak already earns +2%.
| From level | XP to advance |
|---|---|
| 1 | 100 |
| 2 | 193 |
| 5 | 567 |
| 10 | 1,383 |
| 20 | 3,464 |
Level is never stored. It is derived from total XP every time, so the two cannot drift apart. The same pure functions in src/lib/game.ts run on the server (to pay) and in the browser (to predict), which is why the optimistic update always matches what the server returns.
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router, React 19, TypeScript) | Server components give real data on first paint; route handlers give a JSON API in the same deploy |
| Styling | Tailwind CSS v4 + a hand-built design system | Theme tokens in @theme; custom SVG glyphs instead of emoji or an icon set |
| Motion | Motion (Framer Motion) | Springs, layout animations, exit transitions |
| Database | SQLite / libSQL via Drizzle ORM | A plain file locally with zero setup; Turso in production with no code change |
| Auth | Custom — jose (JWT) + bcryptjs | Small, auditable, no third-party session store |
| Validation | Zod | Every request body is parsed and length-capped before touching the database |
| Fonts | Cinzel · EB Garamond · IM Fell English | Via next/font — self-hosted, no layout shift |
Requirements: Node.js 22 and npm.
git clone https://github.com/monickverma/aetherquest.git
cd aetherquest
npm install
cp .env.example .env.local # then set AUTH_SECRET (see below)
npm run setup # creates the tables and stocks the Vault
npm run dev
Open http://localhost:3000 and create a character.
npm run setup creates data/aetherquest.db, a local SQLite file. There is no database server to install. Delete the file and run setup again for a clean slate.
| Script | What it does |
|---|---|
npm run dev | Start the dev server |
npm run build / npm start | Production build / serve it |
npm run typecheck | TypeScript, no emit |
npm run setup | db:push + db:seed |
npm run db:push | Sync the schema in src/db/schema.ts to the database |
npm run db:seed | Upsert the Vault catalogue (idempotent, safe in production) |
npm run db:studio | Browse the database in Drizzle Studio |
npm run test:api | Run the 86-check API test suite against a running server |
Copy .env.example to .env.local.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | yes | file:./data/aetherquest.db locally; libsql://<db>.turso.io in production |
DATABASE_AUTH_TOKEN | production | Turso auth token. Leave empty for a local file |
AUTH_SECRET | yes | Signs session cookies. At least 32 characters. Generate with openssl rand -base64 48 |
NEXT_PUBLIC_SITE_URL | recommended | Canonical origin for SEO metadata, e.g. https://aetherquest.vercel.app |
TURSO_DATABASE_URL and TURSO_AUTH_TOKEN are accepted in place of the two DATABASE_* variables. Those are the names Vercel's Turso integration sets automatically.
Production needs a hosted database, because serverless filesystems don't persist. AetherQuest uses Turso (hosted libSQL, generous free tier).
TURSO_DATABASE_URL and TURSO_AUTH_TOKEN.
Alternatively, create one with the Turso CLI (turso db create aetherquest, turso db show --url aetherquest, turso db tokens create aetherquest) and set DATABASE_URL / DATABASE_AUTH_TOKEN yourself.AUTH_SECRET under Settings → Environment Variables (openssl rand -base64 48).NEXT_PUBLIC_SITE_URL to your production URL.The vercel-build script runs automatically and:
AUTH_SECRET, rather than shipping a site that errors on every request;drizzle-kit push);next build.So the first deploy creates its own tables, and every later deploy keeps the catalogue in step with the code.
Keep the functions next to the database. vercel.json pins functions to bom1 (Mumbai) because this project's Turso database lives in ap-south-1. A completion runs several statements, and each one is a network round trip. Measured on this deployment, moving the functions from iad1 to bom1 took loading the Sanctum from 3.2s to 0.4s, sealing a quest from 3.1s to 0.4s, and fetching quests from 1.2s to 0.27s. If your database is elsewhere, change regions to the nearest Vercel region.
Browser ──────────────────────────────────────────────────────────────┐
GameProvider (confirmed snapshot + pending ops, serial request queue)│
│ names a quest — never sends an amount │
▼ │
proxy.ts ── verifies the JWT, redirects signed-out visitors │
▼ │
Route handlers /api/* ── requireUser() → Zod → mutations.ts │
│ │
▼ │
lib/game.ts (pure progression maths, shared with the browser) ◄────────┘
▼
Drizzle ORM ── libSQL (SQLite file locally · Turso in production)
Data model (src/db/schema.ts)
| Table | Holds |
|---|---|
users | credentials, display name, IANA timezone |
characters | total XP, gold, current and longest streak, last active day, equipped title and sigil |
attributes | one row per character × attribute, holding that attribute's XP |
quests | title, notes, attribute, difficulty, cadence, completion state |
deeds | append-only completion ledger with snapshotted title and the exact award paid |
items | the Vault catalogue (seeded) |
inventory | what each user owns, and what they paid |
rate_limits | fixed-window counters for sign-in and sign-up, keyed by a hash of scope + IP |
The snapshot. Every mutation returns one fully-computed payload (character, attributes, quests, 14-day activity, totals). The browser renders it and never recomputes levels from raw XP.
Optimistic updates without races. The UI shows pendingOps.reduce(apply, confirmedSnapshot). Each optimistic operation is idempotent, so a server snapshot that already contains it is not counted twice. Requests go through a serial queue, so an older response can never overwrite a newer one, and a failure rolls back that single operation and nothing else. You can seal three quests as fast as you can click.
The client names a quest; the server decides what it's worth.
POST /api/quests/:id/complete ignores its body. XP and gold come from the difficulty stored on the quest row and the server-side streak.UPDATE … WHERE … RETURNING inside a transaction. Eight parallel completion requests produce exactly one award (see the test suite).UPDATE … SET gold = gold - price WHERE gold >= price, so two simultaneous purchases cannot spend the same coins.user_id. Touching another player's quest returns 404, which doesn't even confirm it exists.UPSERT … RETURNING, so simultaneous attempts can't slip past, and keys are SHA-256 hashes, so no raw IPs or emails are stored. On Vercel the client IP comes from x-forwarded-for, which the edge overwrites and so can't be spoofed.X-Frame-Options: DENY, X-Content-Type-Options: nosniff, a strict Referrer-Policy, and a restrictive Permissions-Policy.| Situation | Behaviour |
|---|---|
| Empty or whitespace-only quest | Rejected in the browser with focus moved to the field, and again by the server (422, readable message) |
| Connection drops | An offline banner appears; actions are refused with a clear message and optimistic changes roll back. On reconnect the log re-syncs |
| Server error mid-action | The optimistic change is reverted and a toast explains; nothing is left half-applied |
| Double-click or two tabs | Only one completion is recorded; the other gets a polite "already done today" |
| Undo after spending the gold | XP and gold are reclaimed but gold is clamped at zero, so a balance never goes negative |
| Undoing the only deed of the day | The streak is rolled back too |
| Returning after midnight | Daily quests reopen in your own timezone; a lapsed streak reads 0 |
| Quest deleted | Its deeds stay in the Codex under the title they had when completed |
| Invalid timezone sent at sign-up | Falls back to UTC |
| Expired or tampered session | Treated as signed out |
| Render error | Caught by an in-shell error boundary with Try again, so navigation still works |
| Unknown URL | A themed 404 |
header, nav, main, aside), one h1 per page, and a logical heading order.<button> or <a>, so everything works with Tab, Enter, and Space. Each quest's buttons have unique labels, e.g. "Seal 'Read 20 pages' for 51 XP and 22 gold".alertdialog that takes focus and closes on Escape.aria-invalid and aria-describedby, and focus moves to the first invalid field.role="progressbar" with values and descriptive labels.aria-live regions.prefers-reduced-motion removes movement but keeps every state change; forced-colors mode is respected.next/font with display: swap.WebApplication data, robots.txt, sitemap.xml, and a web manifest. Private pages are marked noindex.scripts/api-test.sh runs 86 adversarial checks against a running server:
alg: none), and missing session tokens;xp/gold fields being ignored;npm run dev # terminal 1
npm run test:api # terminal 2 → RESULT: 86 passed, 0 failed
Against a deployment, set BASE_URL:
BASE_URL=https://aetherquest-black.vercel.app npm run test:api # → 76 passed, 0 failed
Ten checks run only locally: eight equip checks grant gold directly in data/aetherquest.db, and the simultaneous rate-limit checks need every request to leave from one IP, which NAT and multi-WAN networks don't guarantee. (The sequential check reuses a single connection so that it holds everywhere.) Remote runs create a few @test.dev accounts. Their rate-limit checks use throwaway addresses, so they don't lock out real sign-ins, but leave 10 minutes between remote runs, because each one uses most of the sign-up allowance for your IP.
src/
├── app/
│ ├── (app)/ signed-in shell: sanctum, codex, vault (+ loading, error)
│ ├── api/ route handlers: auth, quests, codex, vault, character
│ ├── enter/ · enlist/ sign in · sign up
│ ├── page.tsx static landing page
│ ├── globals.css the design system
│ └── robots.ts · sitemap.ts · manifest.ts · opengraph-image.tsx · icon.svg
├── components/ GameProvider, QuestLog/Card/Composer, CharacterSheet,
│ LevelUpRite, SparkLayer, Vault, Codex, glyphs, ui
├── db/ schema + lazy libSQL client
├── lib/
│ ├── game.ts the progression engine (pure, shared)
│ ├── mutations.ts all state changes, transactional
│ ├── queries.ts snapshot, codex, and vault reads
│ ├── auth.ts · session.ts JWT + bcrypt
│ ├── validation.ts Zod schemas
│ └── time.ts timezone-aware day keys
└── proxy.ts route protection
scripts/
├── seed.ts Vault catalogue (idempotent upsert)
├── check-env.ts pre-deploy configuration guard
└── api-test.sh the API test suite
Known dev-only advisory: npm audit reports a moderate esbuild advisory inside drizzle-kit. That package is a development CLI used only for schema pushes; it isn't part of the application bundle, and the fix requires a breaking downgrade.