design system
One ink. One red.
Four rooms.
This page is the design system behind this site and its sibling apps — the code is the spec. Every token, component and motion pattern lives here, and works in all four rooms: try the room switches in the header. When a new component is needed anywhere, it gets designed on this page first.
restraint does the talking: chrome is monochrome, color lives only in photographs, data, and one hand-drawn red.
01 · rooms
Only the paper changes.
Every surface keeps the same bones and swaps only its light: paper for the personal site, night for the party screens and dark mode, porcelain for the dog, stone for the dashboards. A component that only uses room variables works in all four automatically — that's the test, and this whole page passes it.
rooms are assignments, not prisons: the party app lets guests flip paper ⇄ night and remembers the choice. the one exception is the party TV — a giant screen in a dark room stays night, because its darkness is stagecraft, not preference.
02 · voices
Three voices, never a fourth.
Headlines carry the argument.
KICKERS · CAPTIONS · DATA · STATUS — the quiet voice that annotates everything
English leads. The system's entire public hanzi inventory is seven characters — three names and four card seals: 焕 · 暖暖 · 随想 · 造 · 动 · 暖 · 聚. A fixed set, not a pattern: concept names stay English in public copy, and full bilingual text lives only in /notes prose. Body text stays quiet, medium-weight ink for emphasis, links as thin underlines.
scale is the accent color: a 72px headline against 11px mono does what a bright hue would — without the hue. fonts are self-hosted woff2, zero CDN.
03 · the red
One red, four doors.
Vermilion #c3272b — the red-pen ink teachers grade with — is the only color the interface is allowed. It appears through exactly four doors, always as a hand-drawn stroke, never as a geometric block. Everything else is ink, paper, gray.
The marks
One gesture only: the brush swash under the name. Signature, not logo.
Alive signals
Live pulses, form errors, destructive confirms, the newest data point, first place. Things that need attention right now.
Selection
Select any text on this page — the red belongs to whatever is being read.
go ahead, drag across this line.
Her collar
Yumi is the one member of the house the design system doesn't apply to.
two content exemptions, because color belongs to content: photographs keep theirs (hushed — see 05), and data keeps its semantics (gains in muted sage #4e8a6e, losses in vermilion — numbers only, never chrome).
04 · marks
Signature, not logo.
No invented pictorial logo, ever. The identity is typographic and photographic: the name with a red brush-stroke under it, a red dot, or simply a real photo — the avatar is the icon.
og corner, headline keywords
never a signature (door 2 owns it)

a face beats any icon
never let one grow back into a logo
retired, on purpose: a square seal stamp (too official) · a neat circle around the name (too formal) · a casual double-loop scribble (tried it, didn't love it) · any cartoon or geometric logo (someone else's pen) · a red dot as the header mark (to the whole internet a red dot says "notification" — and here it already says "live"). The graveyard stays public — rejected ideas are how the system learned what it is.
05 · photography
Color rests at a whisper.
Photos carry all of the site's warmth, so they follow one law: at rest they wear a hushed grade — 60% saturation with a faint warm cast — that pins every picture into the same album. Hover (or scroll past center, on touch) and the color blooms back to full. Two tokens, --photo-rest / --photo-alive, and no component may hand-write its own filter.



motion-first law: when a slot has both a still and a loop, the loop wins — poster and reduced-motion fallbacks always in place.
frames are rounded rectangles (20px outer / 12px inner) — the old organic blobs are retired. video loops: muted, playsinline, ~0.5MB, poster fallback, lazy off-screen. an earlier version rested photos at pure black & white; it fought the warm paper and read cold, so the whisper replaced it — chosen from a five-grade darkroom lineup.
06 · no emoji
Emoji are someone else's pen.
Interface chrome uses zero color emoji: they're cartoons drawn by a platform vendor, they carry their own hues, and they render differently on every device. Three substitutes, one per pen:
Type does it
Most emoji can simply be deleted — the mono voice was already enough.
🎉 THAT'S A WRAP → THAT'S A WRAP
⚠️ error → ⚠︎ error (text-form dingbat, U+FE0E)
✅ done ×3 → ✓ done ×3
The gray pen draws it
When a picture is genuinely needed — empty states, theme badges, reaction controls — a 2px line doodle in the current ink.
the doodle set grows as needed — shuttlecock, paw, megaphone, cake, crown so far.
The red pen celebrates
Ceremony comes from the system, not a party-popper glyph: first place gets a vermilion rank, warnings get the red hairline, big moments get big type and motion.
- 1team otter
- 2noodle
the one exemption: emoji typed by guests and users render as-is — that's their voice, not our chrome.
07 · the stage
Backgrounds can live — behind glass.
A stage is a layer of living media between the paper and the chrome — a video loop, a photo wall, a slow generative drift. Hosts own what plays on stage; the system owns the proscenium: every stage wears the whisper grade, a room-colored veil, and the grain — non-negotiable, so anyone's media automatically belongs.
that's a wrap
Thanks for coming!
1team otter · 420 pts
video stage — live in the party app: the host uploads the loop, the TV wears it. also the post-emoji ceremony: big type and one red rank, no party-popper glyph
photo stage — two frames, 26s crossfade drift
breathing paper — the zero-asset fallback
- video already wired into the party app: the host uploads a loop in the control room (≤4MB, straight to media storage); the TV wears it on the lobby and farewell only — the moment a question opens, the stage clears. Guest phones never render it.
- photo Ken Burns crossfade; the party farewell can play the night's own guest photos — zero new assets, maximum personal
- scroll hero-only: film at the top, the page pulls the curtain as you read; never a whole-page video
- ownership hosts own the stage, the system owns the frame — the veil, the grade and the grain aren't host-configurable, so anyone's media lands on-brand
- tempo stages move at ≥26s — they're weather, not actors; micro-interactions stay ≤300ms
08 · motion
The page moves because you do.
Everything you touch answers within 300ms; everything that moves on its own is scroll-driven or slower than 26 seconds. A ~40-line JS engine feeds scroll progress to CSS custom properties, because pure CSS scroll timelines silently fail in half the browsers out there. No JS, or reduced motion? The page is already in its final, fully readable state. Zero dependencies, no WebGL.
one stage, five reflexes — bring a pointer and play. everything answers within 300ms.
Cards stand up
rotateX(22°) → 0 as they enter; older browsers get a one-shot rise instead of a scrub.
Ink fills as you scroll
Scroll up and down — the section title floods from gray to ink, left to right.
Stacking photo cards
The homepage opener: four real photos stack one over the last, each developing as it arrives. See it live →
card wording reveals together with its photo — text never sits static while the image performs. big scroll stories are always vertical and always carried by photography: a giant-logo zoom and an ink-bottle pour were both built, both rejected.
09 · components
The working parts.
Forms, data, feedback, navigation, party hardware, prose — every interactive part of every surface, in one place. All of it passes the room test; flip the header switches while you play.
Forms
focus = the border turns ink, no colored halos. tab through — every stop is visible.
Data
| holding | weight | day |
|---|---|---|
| NVDA | 31.2% | ▲ 2.4% |
| TSM | 18.7% | ▼ 1.1% |
ink line, faint grid — the newest point is red because it's alive (door 2).
Feedback
no photos yet — go enjoy the party, upload later
one toast at a time, ink on paper inverted. dialogs are native; destructive buttons wear the red hairline. empty states teach, they don't say "empty".
Navigation & party
teams are told apart by texture, not color — a black-and-white projector and a color-blind guest see the same game.
Prose
Body text stays quiet at 15–16px with a tall line. Medium ink carries emphasis, inline code sits on a panel, and the divider refuses to be a full-width line:
···
Quotes get a 2px ink bar and a little more voice. 双语是两种声部,不是翻译。
10 · the voice
Write like a person.
plainone humane sentence beats three clever ones. empty states teach ("no photos yet — go enjoy the party") instead of announcing emptiness.
specificdecorative copy mines real details, never values-speak. "vibe coding past midnight" made the marquee; the six core values did not — true isn't the same as usable.
english leadsheadlines must stand alone in English; hanzi arrive as single-character seals (造 · 动 · 暖 · 聚) or names (暖暖) — footnotes, not the melody.
no thirdsthe same word or joke doesn't appear a third time on one page. one supervisor joke per page is plenty.
anchoredfloating captions must belong to their image — no joke gets parked on an unrelated photo just because it's funny.
dry humorthe mono voice keeps its spec-sheet deadpan: power draw: one espresso · latency: replies < 24h
11 · the rules
Eight rules derive everything.
rule 1chrome is monochrome; content keeps color (photos, data semantics)
rule 2red passes through four doors only, always as a hand-drawn stroke
rule 3three type voices, never a fourth
rule 4separation is a hairline; shadow means lift, never decoration
rule 5radius: 12–20px outside, 6–10px inside, pills fully round
rule 6everything touchable answers ≤300ms; every animation respects reduced-motion
rule 7components use room variables only — pass that, and all four rooms come free
rule 8new components are born on this page first. if it isn't here, it doesn't exist yet — design it here, prove it in four rooms, then ship it
12 · decisions
The system remembers its arguments.
A design system is a pile of decisions, and half of its value is the rejects. Kept public on purpose:
- 2026-06The warm orange era ends. Cream paper and #f47d31 had run the site since the start — proven, friendly, and increasingly everyone's default.
- 2026-07-03Five palette rounds. Crimson, magenta, marigold, brass, midnight, oxblood, electric azure — all built, all rejected. The lesson: the answer wasn't a hue, it was restraint.
- 2026-07-03Ink and the red pen, ratified. Monochrome ink, one hand-drawn red, four rooms. A square seal stamp and two circle marks were tried and retired the same day — the red pen won.
- 2026-07-03Two openers die. A giant-glyph zoom (logo as protagonist) and an ink-bottle pour (drawn props, diagram energy). The stacking photo cards ship instead — photography carries the big moments.
- 2026-07-03The whisper replaces black & white. Pure grayscale fought the warm paper; a five-grade darkroom lineup settled it at 60% saturation with a warm cast. Same day: the emoji ban.
- 2026-07-04The stage layer. Living backgrounds, host-owned content behind a system-owned proscenium. Marks learn to whisper — signature, not logo. Party rooms become switchable; the TV alone stays night.
- 2026-07-05This page goes live. The lab becomes public — the spec, the components, and the graveyard, all in view-source.
- 2026-07-05The hanzi diet, and motion first. The three-pens metaphor retires to the internal docs; the public hanzi inventory is fixed at seven characters — three names, four seals. And when a slot has both a still and a loop, the loop wins: video is a first-class element now.
- 2026-07-05The dot retires as a mark. One shape, one meaning: a red dot already says "live" here — and to everyone else it says "notification". The swash is the only signature now; even the favicon is a brush stroke.
- 2026-07-06Filter, never on the video. Three loops on this page (and three more on /nuannuan) sat frozen — WebKit doesn't repaint a `<video>` that carries a CSS filter directly, even while it's technically still playing. Fix: filter the wrapper, never the tag. Now a checklist line, so it doesn't happen a second time.
- 2026-07-06Then a second, quieter bug in the same spot. This section's own hero loop still wouldn't play — in Chrome only, Safari was fine. The tell: the stage demo below it (scroll-revealed) played, the hero (a fixed opacity-0-to-1 entrance) didn't. Chrome appears to decide autoplay eligibility the instant a video attaches — one that's still invisible mid-fade seems to get skipped for good. Fix: autoplay video never gets an opacity-fade entrance again, plus a couple of delayed retries as a backstop.