Skip to content

Docs / design system v0.1

Yellowcake

Uranium, refined just enough to be dangerous. This is the kit Fradiation Games is built from: earthling-ui primitives, a reactor's worth of tokens and effects, and components that treat every click like a game mechanic. Everything on this page is live.

00yellowcake

Principles

Yellowcake is the Fradiation Games design system: earthling-ui primitives, dressed for a reactor. Five rules decide the close calls.

  1. 01

    Every interaction is a mechanic.

    Clicks have weight, holds charge up, hovers answer back. But the fun version is never slower than the boring one. If it adds a step, it has to replace a worse one (see HoldButton vs. confirm dialogs).

  2. 02

    Loud chrome, quiet content.

    Glow, glitch and scanlines frame things. They never sit on top of text you need to read, and a game's own screen is never overlaid.

  3. 03

    Deadpan.

    The voice is an Adult Swim bumper: short, dry, a little ominous. Jokes can be lowercase. Anything about money, safety, or data is plain and clear.

  4. 04

    Honest labels.

    Engine, AI involvement and content warnings are always visible on the card, never buried on the detail page.

  5. 05

    The viewer controls the dose.

    Sound is off until they turn it on. CRT scanlines have a switch. prefers-reduced-motion kills all spatial motion. Hotkeys stay out of text fields and running games.

01yellowcake

Brand

A redraw of the original: the glossy trefoil is now geometric (ISO 361 proportions), the green glow ring became a containment ring that opens where the blades point, and the stacked-outline italic wordmark is now outlined Anybody with a rad-green extrusion. Masters live in Figma: ☢ Yellowcake — Brand & Tokens.

<Wordmark />

use hero moments, the HUD, anything that needs to say the name. avoid below 120px wide (use the mono variant or the mark).

<Wordmark />            // color: gradient + rad extrusion
<Wordmark variant="mono" games={false} className="text-bone-100" />

<Trefoil />

use favicon, avatars, loading, and anywhere the name won't fit.

<Trefoil glow />
<Trefoil spin />
<Trefoil variant="mono" ring={false} />

<Lockup />

use headers, email, social cards.

<Lockup className="text-5xl" />

<ReactorCore /> — 3D

use one per page, as the hero. It's interactive: tap it. avoid as decoration in lists; it's a full WebGL context.

// client only — loads three.js lazily
import { ReactorCore } from "@/yellowcake/brand/reactor-core-lazy";
<ReactorCore field className="h-96" />

02yellowcake

Color

Primitives are 'isotopes'. Components use the semantic tokens (earthling-ui's names), so a theme swap is one class. Pairs are checked: bone on lead-950 is 15.9:1, ash-400 on lead-900 is 5.6:1, void on rad-400 is 15.7:1.

lead — neutrals, green-tinted. Backgrounds, surfaces, borders.

void

lead-950

lead-900

lead-800

lead-700

lead-600

lead-500

ash-400

ash-300

bone-100

bone-50

rad — primary. Actions, focus, glow, 'good'.

rad-300

rad-400

rad-500

rad-600

rad-700

rad-900

hazard — secondary. Warnings, loud bands, the wordmark. 500 is the 2019 site's yellow.

hazard-300

hazard-400

hazard-500

hazard-600

core — heat. Gradient base, gamma tier. 600 is the 2019 site's orange.

core-400

core-500

core-600

melt — danger, destructive, meltdown.

melt-400

melt-500

melt-700

cher — Cherenkov blue. Legendary tier, info, AI-assisted.

cher-300

cher-400

cher-600

mutant — glitch splits and AI-generated only.

mutant-400

Semantic tokens

use in components. Never hard-code a primitive where a role exists.

background
foreground
surface
surface-raised
border
outline
primary
secondary
muted-foreground
good
caution
bad
glow
legendary
bg-surface text-foreground border-border
bg-primary text-primary-foreground

.theme-hazard

use one loud band per page: tickers, callouts, the odd principle.

Theme swap

Everything inverts.

Semantic tokens flip; components come along for free.

<section className="theme-hazard">…</section>

03yellowcake

Type

Anybody for display (variable width, run it wide), Archivo for reading, Martian Mono for the HUD, Silkscreen for the loud little pixel bits. All self-hosted through next/font.

Ramp

display-xlMELTDOWN
display-lFresh fallout
display-mThe Vault
titleBushido Assassin
bodyRate it, comment on it, earn rads for it. Mutations unlock as your exposure climbs.
text-hudDose 1,337 rads // Level 4 contaminated
pixelInsert coin

Width is a dial

use expanded for shouting, condensed for dense labels. Drag it.

Fradiation

className="font-display [font-stretch:140%]"

04yellowcake

Motion & sound

Durations: blip 90 · quick 160 · base 260 · slow 480 · epic 900ms (--dur-*). Everything spatial turns off under prefers-reduced-motion. Sounds are synthesized with WebAudio at runtime, so there are no files to download, and they're silent until the viewer turns them on.

Easing

use duration-(--dur-base) ease-(--ease-snap)

--ease-snap
overshoot. Things that clunk into place: toggles, badges, the needle.
--ease-decay
exponential out. Things that settle: cards, panels, counters.
--ease-meltdown
exponential in. Things falling apart: exits, dismissals.

sfx

use sfx.clunk() on commit, sfx.blip() on hover, sfx.tick() for counters.

import { sfx } from "@/yellowcake/sfx/sfx";
sfx.confirm();

05yellowcake

Surfaces & effects

Industrial containers, and the effects that make the place glow. Effects are CSS utilities first; WebGL only where nothing else will do.

<Panel />

use the default container for grouped content.

Specimen data

0x2F

Chamfered equipment panel with a stencil label strip and rivets. The chamfer is drawn by clipped layers, so focus rings inside stay visible.

Hazard edge

For anything with consequences.

<Panel label="Specimen data" meta="0x2f" hazard>…</Panel>

Text effects

use headlines only; one effect per line.

Hover me. Glitch.

EXTRUDED

Core gradient

phosphor glow

<GlitchText text="…" trigger="hover" />
className="text-extrude"   // hard extrusion
className="text-core-gradient"
className="text-glow"

.hazard-stripes

Hazard tape. Add animate-hazard-march for a belt.

.scanlines

CRT lines as a background layer.

.bg-blueprint

Lab graph paper; the default stage.

.grain

Film grain. Static, cheap.

.chamfer

Cut corners. Clips outlines too, so focus rings go on a parent.

.chamfer-top

Top corners only, for trays that rise from the bottom edge.

.shadow-glow-*

rad · hazard · melt · cher. Light, not shadow.

<Marquee />

use a ticker per page, max. Pauses on hover; static with reduced motion.

containment nominal ☢coffee machine critical ☢do not lick the games ☢rads are not a currency ☢
<Marquee label="Bulletins" className="theme-hazard py-2">…</Marquee>

<ReactorField />

use section backgrounds. It's the 2019 site's ripple shader, ported: brand colors, pointer-reactive, renders at low resolution and pauses offscreen.

Move your mouse.

<section className="relative isolate">
  <ReactorField intensity={0.6} />
  <div className="relative">…</div>   // content above; avoid negative z-index
</section>

06yellowcake

Controls

Controls are physical: they sit on a slab, sink when pressed, and click. All of them are real buttons and switches underneath (earthling-ui / ARIA), so keyboards and screen readers get the plain version of the joke.

<RadButton />

use actions. One rad or hazard button per view; ghost for the rest.

<RadButton tone="hazard" size="lg" icon="icon-[lucide--play]">Play</RadButton>
<RadButton asChild tone="ghost"><Link href="/vault">Vault</Link></RadButton>

<HoldButton />

use destructive actions, instead of an 'Are you sure?' dialog. avoid anything that happens often.

Hold with pointer, Space or Enter. Deleted 0×.

<HoldButton action="delete" onConfirm={remove} />

<SafetySwitch />

use settings with consequences: mature content, public visibility, wiping saves.

Show games flagged for gore, language, or themes.

○ Safe

<SafetySwitch label="Mature content" checked={on} onCheckedChange={setOn} />

<Field /> + <TextInput /> / <TextArea />

use every form. Field wires the label, hint and error to whatever control its render prop returns.

13/60

Shown on every card.

That slug is taken.

optional
<Field label="Title" error={err} hint="Shown on the card.">
  {({ id, describedBy, invalid }) => <TextInput id={id} aria-describedby={describedBy} aria-invalid={invalid} />}
</Field>

<ChoiceChips />

use one (or several) of a short list: engine, origin, AI flags, report reasons. Arrow keys move between chips. avoid lists longer than about eight; use a select.

<ChoiceChips choices={ENGINES} value={engine} onValueChange={setEngine} />
<ChoiceChips multiple choices={FLAGS} value={flags} onValueChange={setFlags} />

<Dropzone />

use file intake: builds, covers. Goes hazard-yellow while something hovers over it. Folders get their own button.

Drop a zipped build here, or or .

Demo: nothing uploads.
<Dropzone label="a zipped build" accept=".zip" onFile={read} onFolder={readFolder} />

<SegmentMeter />

use progress with a known end: uploads, charge. Lit segments, like the cabinet loader.

<SegmentMeter value={sent / total} label="Upload progress" tone="rad" />

<CopyField />

use a value people share: playtest links, codes. Focus selects it; the button copies and says so.

<CopyField value={url} label="Playtest link" />

07yellowcake

Rating

0 to 5 in half steps, like Newgrounds. Every band has a word, so a score reads at a glance: Inert, Trace, Warm, Hot, Critical, Meltdown.

<GeigerRating />

use the rating input. Sweep to hear it tick faster; click to lock it in.

Rate this game3.5 · Critical
<GeigerRating value={v} onChange={setV} />

<RatingMeter />

use read-only scores on cards, lists and reviews.

0.5Trace
1.5Warm
2.5Hot
3.5Critical
4.5Meltdown
5.0Meltdown
<RatingMeter value={4.5} />

08yellowcake

Game cards

A game is a collectible: tilt, foil, scanlines, a PLAY reticle. Engine and AI origin are always on the card. (Ratings and play counts here are demo values.)

Tags

use EngineTag + OriginTag on every card and game page. Pixel tags for loud flags only.

FlashThree.jsUnityGodotHTML5HandmadeAI-assistedAI-generatedNew!HotReleasedAS3
<EngineTag engine="godot" />  <OriginTag origin="ai-assisted" />
<Tag pixel tone="hazard">New!</Tag>

<StatusTag /> + <Countdown />

use where a game is in draft → containment → released / buried, and how long judgment has left. GameCard takes flag="contained" and a footer for the countdown.

DraftIn containmentReleasedBuriedRemovedcloses in
<StatusTag status="containment" />
<Countdown to={game.judgmentEndsAt} done="Closing" />

09yellowcake

Exposure & mutations

XP is 'rads'. Rads set your exposure level (0 Clean → 9 Meltdown). Mutations are achievements; rarity is named for radiation: alpha, beta, gamma, and cherenkov for the legendary ones. Games declare their own in fradiation.json and unlock them through the SDK. Signed in, the server keeps score; anonymous progress stays in the browser and its easter eggs transfer when you get a badge.

<DoseMeter />

use (demo meter — the buttons don't touch your real dose)

1

Exposure level

Exposed

165rads absorbed

135 rads to Irradiated

1Exposed165 R
<DoseMeter rads={rads} />
<DoseMeter rads={rads} variant="compact" />   // HUD

Exposure levels

  1. 0Clean0 R
  2. 1Exposed100 R
  3. 2Irradiated300 R
  4. 3Glowing700 R
  5. 4Contaminated1,500 R
  6. 5Mutating3,000 R
  7. 6Mutant6,000 R
  8. 7Radioactive12,000 R
  9. 8Critical25,000 R
  10. 9Meltdown50,000 R

<MutationBadge />

use profiles, game pages, unlock toasts.

Alpha · 25R
Beta · 50R
Gamma · 100R
Cherenkov · 250R

Row two is your real progress. One of them is a secret.

<MutationBadge mutation={m} unlocked={has(m.id)} size="md" />

10yellowcake

Profile

The public profile (/u/handle) is facility ID: clearance (level), status, dose, mutations, and a barcode that's unique to the handle. Badges come from Discord or GitHub sign-in plus a handle claimed on /welcome.

<IdCard />

FRADIATION GAMESFounder // Dev
SF

Name

Steven Frady

@fradiation

Clearance
L7
Status
Radioactive
Dose
13,370 R
Games
8
Reviews
0
Mutations
0

Issued Flash era

<IdCard name="…" handle="…" rads={…} joined="…" mutations={…} />

<ProviderButton />

use sign-in prompts. These are live: they really sign you in.

<ProviderButton provider="discord" />
useSite.getState().setSignInOpen(true) // the full dialog

<Avatar />

use anywhere a person appears in a list. Level pip optional.

G4U0D
<Avatar image={u.image} name={u.name} level={3} />

11yellowcake

Comments

Comments and reviews (Transmission in code). The like button is 'Irradiate' and it doses the reader a couple of rads. Devs are marked. (Demo thread.)

<Transmission />

G
L5
Glowworm@glowworm4.5

played this in 2008 on a library computer and got kicked off for laughing. still holds up. the second level is a war crime.

d
L7
demo_dev@demo_devDEV

the second level is a war crime. that part was intentional.

u
L0
unit_734@unit7342.0

controls feel like steering a shopping cart through soup. 2/5 would get irradiated again.

<Transmission author={u} time="3h ago" rating={4.5}>…</Transmission>

<TransmissionComposer />

use posting and replying. The counter goes hot near the limit; ⌘/Ctrl+Enter sends.

2000 left
<TransmissionComposer onSubmit={async (body) => (await postTransmission({ slug, body })).ok} />

12yellowcake

Feedback

System messages come over the facility PA: hazard-edged toasts in a polite live region. Hover pauses them. Doses float up from where they happened. Anything that needs an answer opens a dialog.

announce()

useSite.getState().announce({ tone: "good", title: "Upload complete", icon: "icon-[lucide--circle-check]" })

applyDose(dose, point)

use after any server action that returns a dose; the rads rise from the pointer and unlocks get announced.

const r = await rateGame({ slug, value });
if (r.ok) useSite.getState().applyDose(r.dose, { x: e.clientX, y: e.clientY });

<RadDialog />

use every popup: forms, setup steps, sign-in, reports. A hatch on desktop, a tray from the bottom edge on a phone. Tone colors the tape: hazard by default, melt for reports and anything destructive, rad for good news. avoid confirming a delete; that's a HoldButton. Status after the fact; that's a toast.

<RadDialog open={open} onOpenChange={setOpen}>
  <RadDialogContent tone="hazard" size="md">
    <RadDialogHeader eyebrow="Label" meta="0x2F">
      <RadDialogTitle>…</RadDialogTitle>
      <RadDialogDescription>…</RadDialogDescription>
    </RadDialogHeader>
    <RadDialogBody>…</RadDialogBody>          // scrolls
    <RadDialogFooter>…</RadDialogFooter>      // stays put; primary last
  </RadDialogContent>
</RadDialog>

13yellowcake

Game stage

The cabinet every game runs in, whatever the engine: title bar, click-to-start, restart, fullscreen, letterboxing. The game only mounts after start. Hosted builds run inside it as a <GameFrame>: a sandboxed iframe on the separate play origin, talking to the site only through @fradiation/sdk.

Bouncerflash · as2 · ruffle

<GameFrame />

use hosted builds (Three.js, Unity, Godot, HTML5), inside a GameStage. Live example: /games.

Sandbox: allow-scripts allow-same-origin allow-pointer-lock allow-popups allow-modals. No top navigation, ever.

Handshake: the game says hello → the site checks it came from this iframe and the play origin → hands over a private MessageChannel port.

<GameStage title={t} width={w} height={h}>
  {() => <GameFrame src={buildUrl(slug, hash)} playOrigin={PLAY_ORIGIN}
    player={viewer} onUnlockMutation={…} onSubmitScore={…} />}
</GameStage>

<Scoreboard />

use per-game boards declared in fradiation.json. Top three lit; the viewer's row marked.

Longest defensehighest wins

  1. 1@glowworm7:11.250
  2. 2@demo_dev5:05.900
  3. 3@unit7343:08.020
  4. 4@halflife1:01.400
<Scoreboard name="High score" format="number" order="desc" top={rows} viewerHandle={me} />

14yellowcake

Docs

Long-form pages are Markdown (content/docs), rendered into Yellowcake: GitHub alerts become callouts, fenced code becomes a code block with a copy button. The raw Markdown stays readable for agents at /docs/<slug>.md.

<Callout />

use a note, tip, warning or caution inside running text. avoid page-level status; that's a toast.

<Callout tone="warning">Paths must be relative.</Callout>

> [!WARNING]   (the same, in Markdown)
> Paths must be relative.

<CodeBlock />

use commands, configs and snippets people copy; wrap for prose, like a prompt. avoid inline code; use <code>.

bash
node check-build.mjs pack Builds/Fradiation
prompt
Use the fradiation MCP server to put this game on Fradiation: wire in the SDK with a few mutations and a high-score board, then deploy it to a draft.
fradiation.json
{
  "viewport": { "width": 1280, "height": 720 },
  "mutations": []
}
<CodeBlock lang="bash" code={cmd} />

15yellowcake

Voice

Adult Swim bumper, not a children's cereal box. Short and dry. The theme lives in the mechanics (rads, mutations, containment) and in the odd line of flavor; navigation, labels, forms and errors just say what they are. If a word needs explaining, it's the wrong word.

Vocabulary

rads
XP. 'You absorbed 10 rads.'
exposure level
user level, 0–9
mutation
achievement / medal
badge
your account: sign-in plus a handle, shown as an ID card
irradiate
the like button on a comment
containment
new-game judgment: 48 hours of release / bury votes
release / bury
judgment votes (Newgrounds' protect / blam)
clearance
the level field on your badge
the Vault
the original Flash games. Unlisted, and locked
the terminal
command palette ( / or ⌘K )
Comments
not transmissions
Ratings
not readings
Details
not specimen data
My games
the developer pages (/dev), reached from the avatar menu; not a themed room name
Invite code
developer access, not a clearance code
Profile
not personnel file

Say it like this

Panel label

Specimen data

Details

Empty state

No games yet. Be the first to upload!

No games yet. Check back soon.

Flavor (one per page, at most)

Welcome to the reactor. Please enjoy your stay!

Everything, contaminated.

Destructive

Are you sure you want to delete?

Hold to delete.

Error (keep plain)

Something went wrong :(

Upload failed: index.html must be at the top of the zip.