尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证evenus.app — the marketing site, answers hub and 115-post blog. Static HTML, no framework.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
The website for EvenUS — an app for two people who live together that
measures how money, time and mental load are actually shared, and suggests one
thing to swap each week.
Static HTML, generated by one Node script. No framework, no dependencies, and
the only client-side JavaScript is src/dc/site.js.
handoff/ holds the design bundle: seven .dc.html page prototypes plus a
shared header and footer.查看完整 README ↗The website for EvenUS — an app for two people who live together that measures how money, time and mental load are actually shared, and suggests one thing to swap each week.
Static HTML, generated by one Node script. No framework, no dependencies, and
the only client-side JavaScript is src/dc/site.js.
node src/build.mjs # -> dist/
npm run serve # build, then serve dist/ on :8080
handoff/ holds the design bundle: seven .dc.html page prototypes plus a
shared header and footer.
Those files are the source of truth, and the build RENDERS them rather than
transcribing them. The handoff asks for pixel-for-pixel, and the surest way to
hit that is to use the designer's own markup instead of a retyping of it —
retyping 46 KB of hand-tuned spacing is how a build ends up 90% faithful in a way
nobody can point at. To change the design, change the file in handoff/.
What is not carried over is the prototype runtime (support.js), which the
handoff explicitly excludes. Every interaction is reimplemented in
src/dc/site.js as plain DOM code:
| Fairness calculator | The app's own formula, ported in src/calc/engine.js (see below) |
| Accordions | Single-open, first open, aria-expanded / aria-controls |
| Blog filter | Six categories, aria-pressed, live count |
| Scroll reveal | IntersectionObserver, 4-second failsafe, respects reduced motion |
src/dc/hooks.mjs annotates the rendered markup for that runtime.
Every anchor it uses is asserted, and that assertion earns its keep:
stroke-dashoffset="57" matches twice in the page because a phone mockup draws
the same ring, and >87< matches four times. Hooks are therefore scoped to the
section they belong to. A calculator that silently stops updating because a
colour moved is invisible in a screenshot — this fails the build instead.
src/build.mjs from the system's own parts: the
shared chrome, the legal pages' prose column, the index's category chip.<sc-for> is driven with the articles in src/posts/ instead.One file per article in src/posts/<slug>.html; the slug is the URL. Metadata
rides in an HTML comment at the top:
<!--
meta:title Headline (under 70 characters)
meta:excerpt Meta description, 120 to 155 characters
meta:read 6 min
meta:cat Mental load | Money | Using EvenUS | Life changes | Reviews
meta:date 19 March 2026
meta:iso 2026-03-19
meta:updated 2026-10-12
-->
Every article is bylined Aapt Dubey and links to /about/. Search titles that
differ from the headline live in src/dc/seo.mjs. Posts merged into another
are listed in MERGED in src/build.mjs and need a matching 301 in
vercel.json; the build fails without it.
/features/ and one page per app feature at /features/<id>/, built by
src/dc/featurepages.mjs. The ids, titles and steps come from the app's own
catalog, copied into src/features/catalog.json by
node src/features/sync-catalog.mjs (re-run when the app's catalog changes;
the build fails if a feature has no page). Each page's words are in
src/features/content/<id>.json, the hub's in src/features/hub.mjs.
Screenshots are listed in src/features/screenshots.json. Save each capture
as static/img/features/<file>; a figure appears only once its file exists,
and the build prints how many are still missing. Sharing cards are committed
PNGs in static/og/features/, made locally with
node src/features/og-cards.mjs (needs Chrome, so it is not part of the build).
handoff/ the design bundle. EDIT HERE.
HANDOFF.md the full brief: tokens, type, per-screen spec
src/
build.mjs the whole build
posts/ the articles, one file each
dc/render.mjs renders .dc.html to static HTML
dc/hooks.mjs annotates it for the runtime, with assertions
dc/site.js the interactions, as plain DOM code
dc/blogdata.mjs categories and read times
dc/seo.mjs search titles and description trimming
dc/checks.mjs what the build refuses to ship
dist/ generated; not committed
Three placeholders remain and the build prints them on every run:
[COMPANY NAME], [COMPANY ADDRESS], [JURISDICTION]. They live in
handoff/Privacy.dc.html, handoff/Terms.dc.html and
handoff/Disclaimer.dc.html. Both app stores gate submission on those pages.
Vercel builds and deploys on push; vercel.json carries the build command
and output directory. Without it Vercel serves 404s for everything, because
dist/ is gitignored and a zero-config deploy finds no index.html.
GitHub Pages also deploys on push, via .github/workflows/deploy.yml.
BASE_PATH is the difference. A GitHub project site is served from /<repo>/,
so every root-relative link 404s without it — breakage that appears only after
deploying. It is applied at the write boundary rather than where links are
authored, because links also arrive from the articles' own
cross-references, which no template controls.
Vercel serves from the domain root and needs no BASE_PATH. Do not copy the
workflow's value into Vercel's environment variables; it would prefix every link
and break the site in the opposite direction.
⚠️ .app is on the browser HSTS preload list. Every .app domain must serve
valid HTTPS — browsers refuse plain HTTP outright, with no click-through.
The app's invite links are https://evenus.app/join/<CODE> (8 characters from
ABCDEFGHJKMNPQRSTUVWXYZ23456789). With the app installed and the two
.well-known files live, the phone opens the app and the website is never
seen. Everyone else gets the join page:
dist/join/index.html is one static page for every code. vercel.json
rewrites /join/:code/ to it (trailingSlash: true first 308s
/join/CODE to /join/CODE/, which is why the rewrite source ends in /).
The code is read from the address bar by an inline script, checked against
the alphabet and length, and shown with "Copy code" and "Open in EvenUS"
(evenus://join/<CODE>). Nothing is looked up, so it cannot say whether a
code has expired.noindex (meta tag and X-Robots-Tag), no-referrer, and loads no
analytics at all until the visitor accepts in the cookie banner; even then
it reports /join/ with the code stripped. The code is never in the title.src/build.mjs (search for "Invite landing")./join/<CODE> 404s there. Vercel
is the host that matters for this.The .well-known files (src/applinks.mjs) are written only when the
owner's values exist, read from the environment or from a gitignored
.env.local at the repo root (the environment wins):
# .env.local (never committed)
APPLE_TEAM_ID=ABCDE12345
ANDROID_SHA256_FINGERPRINTS=AA:BB:...:FF,11:22:...:EE # Play app-signing key, then the EAS keystore
On Vercel, set the same two names under Project → Settings → Environment Variables. With both set the build writes:
dist/.well-known/apple-app-site-association (no extension), for
<TEAMID>.evenus.app, paths /join/*dist/.well-known/assetlinks.json, for package evenus.app with every
fingerprint givenA missing value skips that file and prints a TODO: line; the build still
passes and never ships a placeholder. A value that is present but malformed
(not 10 characters, not 32 colon-separated hex pairs) stops the build. Where to
find the values: the app repo's docs/18_APPLE_SUBMISSION.md (Team ID) and
docs/17_PLAY_SUBMISSION.md (fingerprints).
vercel.json sets Content-Type: application/json on both, and the build
fails if those headers or the join rewrite go missing, or if any redirect
touches /.well-known/* or /join/*. Vercel exempts /.well-known/* from
the trailing-slash redirect, so both files are served with no redirect.
⚠️ That exemption is a final route in Vercel's routing, and it sits before the
headers routes, so Vercel may serve the files without the headers in
vercel.json. assetlinks.json gets application/json from its extension
either way. Check the other after the first deploy with the values set:
curl -sI https://evenus.app/.well-known/apple-app-site-association # expect 200, no Location, content-type application/json
curl -sI https://evenus.app/.well-known/assetlinks.json
curl -s https://app-site-association.cdn-apple.com/a/v1/evenus.app # what Apple's CDN has cached
Current iOS reads the file through Apple's CDN and accepts it without the
JSON content type, so this is unlikely to matter, but if it does, replace
trailingSlash: true with an equivalent redirect that skips /.well-known/.
The homepage widget and /calculator/ run the EvenUS app's formula for one
complete week with nothing carried over (src/calc/engine.js, prepended to
site.js by the build). It is a port, so it is checked against the real thing:
npm run test:engine # 2,500 random weeks through the app's engine and the port; any difference fails
npm run golden # 5,000 weeks, then rewrites src/calc/golden.json (50 cases)
test:engine imports ../evenus/src/lib/fairness.ts (or $EVENUS_APP) at
test time only; nothing from the app ships. The build cannot see the app repo,
so it checks the port against src/calc/golden.json instead, and checks that
the homepage widget's figures in the design file are what the engine gives.
Either drifting fails node src/build.mjs. After any change to the app's
engine, run npm run golden and commit the new table.