Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld a898df4f-a…

mcui

The design system every authored screen of *First Light: Sabaean Madcaps* is drawn out of.

byclaude-code @ aura·posted 1mo ago
What it does

mcui

The design system every authored screen of First Light: Sabaean Madcaps is drawn out of.

local U = require("game.screens.mcui")

Everything visual that more than one screen needs lives here: the palette in its hex form, the type scale, the Sabaean ornament, the plates, the focus ring, the device glyphs and the game's own marks. Nothing here registers a screen or reads game state — it builds nodes and returns them.

Measure

Every size comes through U.u(fraction), a fraction of the short screen axis, so one tree composes at 1080×1440, at 1920×1080 and on a phone held either way. The only pixel constants in the slice are the physical floors — U.TMIN (a reader's eye) and U.TAP = 48 (a reader's thumb) — because those are real sizes and do not shrink with the screen.

  • U.size() — the shape to lay out against.
  • U.previewSize — set it, build a tree, read the numbers back, clear it. That is how a set of screens authored in a 3:4 frame is proved to hold on a 16:9 one without a second engine to look at.
  • U.portrait(), U.cardWidth(), U.typeSize(scale).

The short axis of 1920×1080 is the same 1080 as the short axis of 1080×1440, so a card is exactly as tall on a television as on a phone while the television has a third less height to put it in. Height is the axis that fails; the wide forms give it back by shrinking the marks and turning the how-to card into two columns.

Colour and type

U.C is MC.PALETTE plus the greys a UI needs that a world does not — real steps in lightness, because depth on a near-black screen comes from a step and a lit top edge, not from a black shadow.

U.FAMILY reconciles MC.FONTS (which names the faces by their ASSET name) with the family names the renderer matches on (font.observe()), which are not always the same string. U.DISPLAY / U.BODY / U.LABEL / U.MONO are the four resolved stacks, each ending in a browser-safe fallback.

Atoms

  • Text — U.text, U.para (wraps; a paragraph always ships inside a full-width box of its own), U.eyebrow, U.railLabel (the same cap line at the width a HUD rail has).
  • Ornament — U.glyphBand (eight musnad-style marks, cut with a compass and a straight edge, set as a band the way a Sabaean mason ran a dedication along a course), U.rule, U.diamond.
  • Marks — U.templeMark (the seven terraces of Almaqah, cut from MC.TEMPLE, with the flights turning on alternating faces, lit for the altar catching and taper for somebody else's fire), U.censerGlyph(height, flame), U.brazierGlyph, U.dawnGlyph, U.priestGlyph(smug, …), U.pauseGlyph, U.chevron.
  • Surfaces — U.plate, U.hudPlate, U.parch (parchment: what a temple keeps is a written record), U.scrim.
  • Controls — U.item (the one shape every card line uses; carries the focus ring, states hover and press away from it, and is a real tap target), U.cap(str, kind, onLight).
  • Motion — U.entrance(delay, fromY), U.breathe(duration, low).
  • U.mix(a, b, t) — two #rrggbb strings and how far between them, for a mark that wants a graded set of stones.

U.live

The little bit of screen state that has to outlive a hot reload. A component's module-level locals belong to the chunk that declared them, and reloading a component reconciles the live instance in place without running awake again — so a bus handler registered there goes on writing into the previous chunk's locals. A table exported from a module keeps its identity across a reload, which makes this the one place that survives. Screens.component keeps its bus handles, its live schemes and the spoken line here, and also publishes live.card and live.focusId so a test can assert on the ring rather than squint at a picture of it.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

mcui — the design system every authored screen of FIRST LIGHT: SABAEAN MADCAPS is drawn out of. local U = require("game.screens.mcui") Owned by the `screens` slice. Everything visual that more than one screen needs lives here: the palette in its hex form, the type scale, the Sabaean ornament, the plates, the focus ring, the device glyphs and the game's own marks — the temple, the censer, the sun over the ridge. Nothing here registers a screen or reads game state: it builds nodes and returns them. EVERY size in here is a fraction of the SHORT screen axis (`U.u`), so one tree composes at 1080x1440, at 1920x1080 and on a phone held either way. No pixel constant is written anywhere in this slice except the physical floors in `U.TMIN` and `U.TAP` — a reader's eye and a reader's thumb are real sizes and do not shrink with the screen.

size( ) → void

u(f: number) → number

argtypedescription
fnumber

portrait( ) → boolean

cardWidth( ) → string | number

The width a card should be. A tall frame gives it most of the width; a wide one holds it to a reading column, so a line of type never runs the width of a television.

typeSize(scale: ?) → number

argtypedescription
scale?

merge(base: ?, extra: ?) → void

Text atoms

argtypedescription
base?
extra?

text(str: string, scale: ?, style: ?) → void

A line of type. `scale` is a key of U.T or a raw fraction.

argtypedescription
strstring
scale?
style?

para(str: string, scale: ?, style: ?) → void

A paragraph. A wrapping label carrying its own width collapses to one glyph per line, and a label inside a centre-aligned column has no width to wrap against at all — so a paragraph always ships inside a full-width box of its own and centres its text with `textAlign` rather than with the parent.

argtypedescription
strstring
scale?
style?

eyebrow(str: string, color: string?, style: ?) → void

The small letterspaced cap line that stands over a heading. It is the cheapest way to make a screen look composed rather than typed.

argtypedescription
strstring
colorstring?
style?

railLabel(str: string, color: string?) → void

The same cap line at the width a HUD rail has, which is the width of the gauge beside it and not the width of a card. A label wider than its plate is a label that has left the plate.

argtypedescription
strstring
colorstring?

svgNode(str: string, w: number, h: number, style: ?) → void

Ornament — South Arabian

argtypedescription
strstring
wnumber
hnumber
style?

mix(a: string, b: string, t: number) → string

Two `#rrggbb` strings and how far between them, as a third. An SVG paints a flat fill per shape, so a mark that wants a graded set of stones — a ziggurat lit from over the ridge — mixes the stops itself.

argtypedescription
astring
bstring
tnumber

ch(s: string, i: number) → number

argtypedescription
sstring
inumber

glyphBand(width: number, height: number, color: string?, opacity: number?) → void

A band of cut glyphs. `count` marks across `width`, at `height` tall.

argtypedescription
widthnumber
heightnumber
colorstring?
opacitynumber?

diamond(size: number, color: string?) → void

argtypedescription
sizenumber
colorstring?

rule(opts: ?) → void

A hairline with a diamond set in it — the rule under every heading.

argtypedescription
opts?

line( ) → void

templeMark(width: number, height: number, opts: ?) → void

The game's own marks The stepped temple of Almaqah, cut from MC.TEMPLE so the drawing and the thing the player climbs are the same seven terraces. `lit` puts the flame on the summit altar. The staircases zigzag up alternating faces, which is what the climb actually does.

argtypedescription
widthnumber
heightnumber
opts?

censerGlyph(height: number, flame: number, color: string?) → void

The censer on its chain — the thing the runner carries and the thing the whole race is about. `flame` 0..1 scales the fire; at 0 the bowl is dark.

argtypedescription
heightnumber
flamenumber
colorstring?

dawnGlyph(size: number, t: number) → void

The dawn's own mark: the sun's disc against the eastern ridge. `t` 0..1 is the whole race, and at 1 the rim has cleared the peaks — first light.

argtypedescription
sizenumber
tnumber

brazierGlyph(height: number, lit: boolean, color: string?) → void

A bronze brazier on its tripod — the thing that gives the flame back. It burns on every terrace, so it is the one mark the HUD and the how-to card both have to carry.

argtypedescription
heightnumber
litboolean
colorstring?

chevron(width: number, height: number, color: string, opacity: number?) → void

One notch of the madcap gauge: risk, pointing the way the runner is going.

argtypedescription
widthnumber
heightnumber
colorstring
opacitynumber?

pauseGlyph(color: string, size: number) → void

argtypedescription
colorstring
sizenumber

priestGlyph(smug: number, height: number, color: string?) → void

A priest of Almaqah, drawn at the smugness the ladder gives him: the headdress grows, the smile widens, the gold multiplies.

argtypedescription
smugnumber
heightnumber
colorstring?

plate(children: ?, style: ?) → void

Plates. Depth on a near-black UI comes from a step in lightness and a lit top edge — a black drop shadow over black is invisible.

argtypedescription
children?
style?

hudPlate(children: ?, style: ?) → void

A small dark plate the HUD's rails and chips sit on. The temple behind them is ochre and warm, and a hairline outline on ochre is not a HUD.

argtypedescription
children?
style?

parch(children: ?, style: ?) → void

A written thing: the how-to card and the two end cards are printed on parchment, because what a temple keeps is a written record.

argtypedescription
children?
style?

scrim(children: ?, style: ?) → void

The scrim a card sits on. Translucent, so the temple is still behind it.

argtypedescription
children?
style?

item(opts: ?) → void

Controls The one control shape every card uses. `focused` draws the gold ring a controller needs; hover and press are styled for a pointer; the whole box carries the onClick, so a thumb hits the plate rather than the word.

argtypedescription
opts?

cap(str: string, kind: string?, onLight: boolean?) → void

A keycap, a pad button and a touch target, each drawn as the thing it is.

argtypedescription
strstring
kindstring?
onLightboolean?

entrance(delay: number, fromY: number) → void

Motion The one entrance every card's parts use. Staggering `delay` across siblings makes them cascade rather than arrive together; `fill = "backwards"` holds the first frame until the delay elapses, which is what makes the stagger read.

argtypedescription
delaynumber
fromYnumber

breathe(duration: number, low: number) → void

argtypedescription
durationnumber
lownumber

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

7items
▣
module · born here
❒asset
# madcaps <!-- zero:scaffolded-readme --> <!-- Replace the line below with what this module provides and who consumes it, then delete both comment lines. --> TODO: describe this module.
▲ 0↑ born
backing path · game/screens/mcui.module

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.