Documentation

Rules and presentation

Shared framework reference

BWSS styles

Node types

Shared by both page surfaces:

type Notes
"Box" Generic container. Children: BWSS nodes or strings (text).
"Image" { src: GameImageKey, alt?, style? }. Resolved through the game's images map; unknown keys render nothing.
"Svg" { viewBox: {width,height}, paths: [{d, fill?, stroke?, ...}], style? }. Only <svg> + <path> are emitted.

There is no Text node : text is a plain string in a Box's children array.

Box

type BoxNode = { type: "Box"; style?: BwssStyle; children?: (BwssNode | string)[] };

On the rules surface Box also takes an id, the target of a #fragment link.

Image

type ImageNode = { type: "Image"; src: GameImageKey; alt?: string; style?: BwssStyle };

src is a GameImageKey : a key for media already uploaded to the game. You don't invent keys; reference ones the game has. Unknown keys render nothing.

Svg

type SvgNode = {
  type: "Svg";
  viewBox: { width: number; height: number };
  paths: SvgPathChild[];
  style?: BwssStyle;
};
type SvgPathChild = {
  d: string;                                            // SVG path-data string
  fill?: string;
  stroke?: string;
  strokeWidth?: number | string;
  fillRule?: "nonzero" | "evenodd";
  strokeLinecap?: "butt" | "round" | "square";
  strokeLinejoin?: "miter" | "round" | "bevel";
  opacity?: number | string;
};
  • Only <svg> and <path> are emitted. No <g>, <defs>, <linearGradient>, <use>, <foreignObject>, <script>, <text>, <image>.
  • d is a character-class-validated string: digits, command letters (MLHVCSQTAZmlhvcsqtaz), whitespace, +-.eE,. Anything else (incl. <>"'();) is rejected.
  • Caps: max 16 paths per Svg; max 4000 chars per d; max 16,000 chars total across paths; viewBox sides ≤ 10,000.

BwssStyle : the allow-list

Strict (unknown properties throw). Categories below; values are typed.

Layout

display, flexDirection, flexWrap, justifyContent, alignItems, gap,
flex, flexBasis, flexGrow, flexShrink,
width, height, minWidth, maxWidth, minHeight, maxHeight,
padding, paddingX, paddingY, paddingTop, paddingRight, paddingBottom, paddingLeft,
margin,  marginX,  marginY,  marginTop,  marginRight,  marginBottom,  marginLeft,
position, top, right, bottom, left,
overflow

display: "flex" | "block" | "inline-flex" | "none". position: "relative" | "absolute". overflow: "hidden" | "auto" | "visible".

Typography

fontSize, lineHeight, letterSpacing,
fontWeight, fontStyle, fontFamily,
textAlign, textTransform, whiteSpace, textOverflow,
textDecorationLine, textDecorationStyle, textDecorationColor,
textDecorationThickness, textUnderlineOffset,
color, textGradient

fontWeight: "normal" | "bold" | "100"–"900". fontFamily: "sans" | "serif" | "mono" or a GameFontKey string. textAlign: "left" | "right" | "center" | "justify". textTransform: "none" | "uppercase" | "lowercase" | "capitalize". whiteSpace: "normal" | "nowrap" | "pre" | "pre-line" | "pre-wrap". textOverflow: "clip" | "ellipsis". textDecorationLine: "none" | "underline" | "overline" | "line-through" | "underline overline" | "underline line-through" | "overline line-through" | "underline overline line-through". textDecorationStyle: "solid" | "double" | "dotted" | "dashed" | "wavy".

A text decoration draws in currentColor, and textGradient sets that to transparent. On a node that uses both, set textDecorationColor explicitly or the line is drawn but never visible.

Background

backgroundColor, backgroundImage, backgroundGradient,
backgroundSize, backgroundPosition, backgroundRepeat

backgroundGradient is a structured linear | radial array (max 4 layers). No raw CSS linear-gradient(...) strings.

Border

borderRadius,
borderWidth, borderStyle, borderColor,
borderTopWidth, borderTopStyle, borderTopColor,
borderRightWidth, borderRightStyle, borderRightColor,
borderBottomWidth, borderBottomStyle, borderBottomColor,
borderLeftWidth, borderLeftStyle, borderLeftColor

borderStyle (and per-side): "none" | "solid" | "dashed" | "dotted". There is no border / borderTop shorthand : split into width / style / color.

Effects

boxShadow, opacity, filter, clipPath, transform
  • boxShadow: "none" OR an array of structured layers { offsetX, offsetY, blur?, spread?, color, inset? }.
  • opacity: number in [0, 1] or "0%"–"100%".
  • filter: array of single-key function objects: { blur }, { brightness }, { contrast }, { grayscale }, { hueRotate }, { invert }, { saturate }, { sepia }, { dropShadow: {...} }. Max 8 entries.
  • clipPath: a single-key object: { polygon }, { circle }, { ellipse }, or { inset }.
  • transform: array of single-key function objects: { rotate }, { translateX }, { translateY }, { scale }. Max 4 entries.

Value types

Color (color, backgroundColor, borderColor, boxShadow[*].color, dropShadow.color, Svg fill / stroke)

One of:

  • "#abc" / "#abcdef" / "#abcdef12" (hex, 3/6/8 digits)
  • "rgb(r, g, b)" / "rgba(r, g, b, a)" with numeric args
  • a named CSS color
  • a theme-token ref: "$colors.surface.canvas", "$semanticTokens.fg.muted", etc. (
    lt;segment>(.<segment>)+
    , alphanumerics + _)

No url(), var(), calc().

Length (width, padding, top, borderRadius, ...)

One of:

  • a number (treated as px)
  • a unit string: <number>(px|%|em|rem)
  • a $theme-token ref

No calc(), no vh / vw / vmin / vmax / ch / ex.

A length is a single value. Multi-value shorthands such as "padding": "12px 28px" or "margin": "0 0 16px 0" are rejected; use paddingX / paddingY (or marginX / marginY) or the per-side properties instead.

Theme tokens

A theme token ref starts with $, has at least two dotted segments, and only contains alphanumerics, _, and .:

$colors.surface.canvas        // OK
$semanticTokens.fg.muted      // OK
$spacing.lg                   // OK
$linear-gradient(...)         // REJECTED — parens disallowed
$colors                       // REJECTED — needs ≥ 2 segments

The resolver returns values that have already been validated as colors / lengths. An unknown token resolves to nothing.

Gotchas

  • Style validation is .strict(): an unknown style property (e.g. transform typoed as tranform, or background shorthand) is REJECTED, not silently dropped.
  • No CSS shorthand parsing anywhere : border, background, font, transition, animation etc. are absent by design.
  • Image.src is a GameImageKey string : unknown keys render nothing.
  • Svg.paths[*].d is character-class restricted: digits, MLHVCSQTAZmlhvcsqtaz, whitespace, +-.eE, only. URLs, parens, and quote marks throw.
  • Text inside a Box is a plain string child : there's no Text node. To style text, set typography style fields on the enclosing Box.

Need a hand with this?

Join our Discord