尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A living travel diary. Not an interactive travel blog.
Don't decorate the writing. Animate the meaning.
The reader should not feel like they are scrolling a website with animations added to it. They should feel like they are moving through a memory.
npm install
npm run dev # http://localhost:3000
/ — the archive. Stories are doorways, not article cards./stories/gokarna — the reference story, annotated end to end./studio — paste a raw draft, get a first pass, copy out the MDX.Never:
paragraph → paragraph → paragraph → image
Always:
line → voice → body → gesture → pause → line
Every beat is one thing said, in one voice, occupying space in one way, optionally answered by one doodle in the margin.
A story is an .mdx file in content/stories/. There is no registry —
the directory is the registry, and the filename is the slug. Write the
file and it is on the site.
A bare paragraph is already a beat in the SPEAK voice. That is the single most important thing about this setup: prose reads as prose in the source file, and only the lines that need a different voice get wrapped. The 70/20/10 ratio is therefore the default rather than a discipline.
---
place: "GOKARNA"
date: "14.03.2026"
fragment: "The night bus, and the ten minutes after I got down."
accent: "#2B3ED0"
---
<Scene id="the-sea">
The sea arrives before I see it.
<Shout>Salt first.</Shout>
<Speak doodle="bus" side="right" gesture="escape">
I get down. The bus goes on without me, red lights going small.
</Speak>
<Hold beats={2} />
</Scene>
Write the piece first, with no design in your head at all. Then annotate the beats that earn it. Then delete half of those.
Four independent axes, defined in lib/vocabulary.ts. Add to them only
when a story genuinely cannot be told with what is here — the constraint
is what makes the site read as one medium instead of a pile of effects.
| for | looks like | |
|---|---|---|
Speak | narration, transitions, most of everything | body serif, grounded, no movement |
Whisper | intimacy, vulnerability, the observation you'd lower your voice for | small, soft, a lot of air |
Shout | the turn, the revelation, the joke | display serif at scale, overshoots on arrival |
Thought | what you didn't say out loud | handwritten, loose baseline |
Drift | dreams, water, disorientation, time behaving strangely | words separated and nudged off the line |
Echo | the phrase that will not leave | repeats, shrinking and fading until gone |
Listen | the last line, or the one before the silence | nearly empty screen, one line |
Ledger | a time, a fare, a distance, a date — the factual aside | mono, tracked, in the accent, ruled down its left side |
Shout, Echo and Listen stop working if you use them twice in a page.
tiny · normal · oversized · scattered · offset · handwritten ·
compressed · centered · edge · floating
Each voice has a body it wants; pass body= only to argue with it.
follow · underline · react · become · escape · collide ·
loop · scribble · reveal · trace · breathe
The doodle is a second narrator. It can answer the line, contradict
it, or get ahead of it — a <Margin> beat with no words is how the
reader sees something before the sentence admits it.
<Margin doodle="road" becomes="palm" gesture="become" side="left" />
enter · grow · move · transform · disappear · hold
hold is the important one. Some scrolls should produce nothing.
<Hold beats={3} /> is a deliberate silence — roughly beats × 22vh of
nothing. A blank line in the studio becomes one automatically.
Silence is an element, not leftover space. A story with no pauses has no pacing, and the studio will say so.
The writing takes about 60% of the frame; the rest is stage. Doodles live
in components/doodles/registry.ts as a handful of points in a 100×100
box. The renderer jitters them per instance and smooths them into a
stroke, so no two are drawn the same way twice — the imperfection is
generated, not faked.
To add one, add points. Resist building a set.
What is ahead of the reader sits blurred at its real size in its real
place: you see the shape and weight of what's coming but can't read it.
It resolves on arrival and never veils again, so you can always look
back. A story can opt out with veil: false in its meta.
The class is applied by script, so a page with JS disabled still reads in
full and nothing is ever parked at opacity: 0 waiting on an observer.
Roughly:
A 500-word story might hold five to eight genuinely animated moments. They work as emotional punctuation; more than that and they stop being punctuation and start being wallpaper.
lib/annotate.ts enforces this: when a draft pulls more voices than the
budget allows, the least confident of them are demoted back to Speak.
The engine loses its favourites first.
Before a story is finished:
/studio checks the mechanical half of that list. The rest is yours.
app/
page.tsx the archive
stories/[slug]/page.tsx frontispiece + the story + colophon
studio/page.tsx the first-pass workbench
globals.css voices, bodies, choreography, the veil
components/
living/ Beat, voices, Scene, Hold, Margin, Marks, StoryFrame
doodles/ the registry and the renderer
content/stories/ the writing
lib/
vocabulary.ts the four axes, typed
annotate.ts the first pass, the ratio guard, the quality bar
mdx-components.tsx why a bare paragraph is already a beat
Someone handed me their travel journal. Except the ink moves. The margins have a life of their own. And sometimes the page remembers things before I do.