TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
AetherQuest는 하고 싶던 일을 레벨업 캐릭터로 바꾸어 습관을 퀘스트로 추적하고 XP·골드와 연속 기록으로 다섯 능력치를 올린다
AetherQuest turns the things you already meant to do into a character worth levelling. Track habits as quests, earn XP and gold, build a streak, and watch five attributes rise — with every number kept
생산성TypeScript · CSSGitHub @monickverma온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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 linkREADME 전문 보기 ↗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.