Layout Functions
Layout Functions
Section titled “Layout Functions”Boxes, titled rules, columns, labelled fields, trees and pictures, described once. A telnet client gets box art, the same kind ALIGN(), CENTER() and REPEAT() draw. The web portal draws the same layout as page structure: a bordered card, columns that wrap on a narrow screen, a definition list, a nested list, a real picture.
This topic covers the functions, how layouts nest, their width, what each kind of reader is sent, and the options every layout function takes, then ends with examples.
Functions
Section titled “Functions”| Function | Draws |
|---|---|
| BOX() | a frame with a title in its top edge |
| RULE() | a line across, with a title in it |
| FLEX() and ITEM() | things side by side |
| FIELDS() | labels and values, the values lined up |
| TREE() and NODE() | items under their parents |
| FIGURE() | a picture, with text art for a terminal |
| GAUGE() | a bar filled to a value |
| BULLETS() | a bulleted or numbered list |
| GRID() | short items in as many columns as fit |
| DATATABLE() and DATACOLUMNS() | a table that gives way on a narrow screen, given by rows or by columns |
| GRADIENT() | text shaded through colours |
| BADGE() | a coloured status tag |
| NOTICE() | a message led by the badge of the system it comes from |
| THEMES(), THEME() and SWATCH() | colour themes for layouts; see LAYOUT THEMES |
Text and nesting
Section titled “Text and nesting”The result is text, so strlen(), mid(), edit() and listen patterns all work on the box art. A layout cut or edited by another function is shown as the text it now is, in the portal too.
Layouts nest. A flex(), fields() or tree() on a line of its own inside a box() body becomes columns, fields or a tree inside the box, and a rule() becomes a divider meeting the box’s sides.
Every layout function takes a width. Leave it empty, or write auto, and the layout is drawn at the width of the connection that ran the command (78 if it never said). Each telnet reader is then sent it again at the width their own client reported. A number fixes the width for everyone.
Readers
Section titled “Readers”A client that cannot show Unicode is sent ASCII borders; see LAYOUT BORDERS. A client that says it is a screen reader is sent the content alone, in reading order: no borders, fields as Label: value lines, tree levels as indentation.
Options
Section titled “Options”The layout functions take their options as one JSON object: {"border":"double","pad":2}. Write it in one of two ways:
- straight into the argument, inside a second pair of braces, because the outer pair only keeps its commas together:
box(Hello,,30,{{"border":"double","pad":2}}) - built with JSON():
box(Hello,,30,json(object,border,"double",pad,2))
Written straight into an argument, a JSON list needs \[ in place of its opening bracket, since [ starts a function call: {{"title":\["( "," )"]}}. The same goes for a [ inside a string. ] can stay as it is.
Values
Section titled “Values”| Option | Takes |
|---|---|
| a number option | a JSON number |
an on-or-off option ("vertical", "across", "mirror") | true or false |
"stripe" | true, false or a string |
"theme" | a string or an object; see LAYOUT THEMES |
| every other option | a string |
A string keeps its colour, so "top":"[ansi(hb,=)]" draws a blue edge; a double quote inside one is written \\".
Options apply in the order written, except a preset ("border", "guide"), which applies first so the pieces written beside it change it whatever their order.
Errors
Section titled “Errors”Every function answers bad options the same way:
| Error | When |
|---|---|
#-1 LAYOUT OPTIONS MUST BE A JSON OBJECT | the text is not one JSON object |
#-1 UNKNOWN LAYOUT OPTION <KEY> | a key the function does not take |
#-1 DUPLICATE LAYOUT OPTION <KEY> | a key given twice |
#-1 ARGUMENT OUT OF RANGE | a number outside its range |
#-1 INVALID ARGUMENT | any other value it cannot read |
Examples
Section titled “Examples”A finger sheet
Section titled “A finger sheet”> think box(fields({{"cols":2}},Sex,Male,Species,Human,Job,Dark Warrior,Online,1h)%r[rule(Quote)]%rHooooo?,Mannaz Byron,60)+=====================< Mannaz Byron >=====================+| Sex: Male Job: Dark Warrior || Species: Human Online: 1h |+=========================< Quote >========================+| Hooooo? |+==========================================================+A stat sheet
Section titled “A stat sheet”> think box(fields({{"leader":".","cols":2}},Strength,3,Dexterity,4,Stamina,3,Charisma,2,Manipulation,1,Appearance,3),Attributes,56)+====================< Attributes >====================+| Strength.: 3 Charisma....: 2 || Dexterity: 4 Manipulation: 1 || Stamina..: 3 Appearance..: 3 |+======================================================+A who list
Section titled “A who list”> think box(flex({{"gap":1}},item(Mannaz%rRaya%rTomas,12),item(0s%r5m%r2h,5),item(Hooooo?%rWriting a scene%rAFK)),Who's Online,50)+================< Who's Online >================+| Mannaz 0s Hooooo? || Raya 5m Writing a scene || Tomas 2h AFK |+================================================+A picture beside a sheet
Section titled “A picture beside a sheet”> think box(figure(https://example.com/mannaz.png,Mannaz,/|_/|%r=^.^=%r%b> <,left,fields(,Sex,Male,Species,Human,Job,Dark Warrior)),Mannaz Byron,44)+=============< Mannaz Byron >=============+| /|_/| Sex: Male || =^.^= Species: Human || > < Job: Dark Warrior |+==========================================+In the portal the picture shows in place of the cat, with the fields beside it.
::: seealso
- BOX()
- RULE()
- FLEX()
- FIELDS()
- TREE()
- FIGURE()
- GAUGE()
- BULLETS()
- GRID()
- DATATABLE()
- DATACOLUMNS()
- GRADIENT()
- BADGE()
- NOTICE()
- LAYOUT BORDERS
- LAYOUT THEMES
- ALIGN() :::
Layout Borders
Section titled “Layout Borders”box() and rule() draw with a border style. The game’s default is the layout_border option, double unless the game changes it; "border":"<style>" picks another. A client without Unicode is sent double as the mush style shown below.
Styles
Section titled “Styles”+=====< Title >====+ +-----< Title >----+| mush | | ascii |+==================+ +------------------+The other styles are drawn with Unicode box-drawing lines:
single- light linesdouble- double linesheavy- thick linesrounded- light lines with rounded corners
Each sets its title between small tee brackets in the top edge. A client without Unicode is sent them in ASCII, as described under ASCII clients below. The examples in these topics show the ASCII form.
"border":"none" draws no frame, only the title and the padded body.
Borders inside other layouts
Section titled “Borders inside other layouts”The border options are not only for box() and rule(). Given to any layout that holds others, such as box(), flex(), fields(), tree(), bullets(), grid() or datatable(), they set the border of every box and rule inside it that names none, the way layout_border does for the whole game. A box or rule that names its own keeps it.
> think flex({{"border":"ascii"}},box(Left,,12),box(Right,,12,{{"border":"double"}}))+------------------------------------+ +====================================+| Left | | Right |+------------------------------------+ +====================================+Border pieces
Section titled “Border pieces”Any part of the style can be replaced, after "border" picks the starting point:
corner(all four), ortl,tr,bl,brone at a timetop,bottom;side(both), orleft,rightteel,teer- where a divider meets the left and right sidesopen,close- the brackets round a title
An edge is a pattern repeated along it, so "top":"=-" alternates and "top":"[ansi(hb,=)]" is blue.
> think box(Two fields,,30,{{"side":" ","corner":" ","top":"~","bottom":"~"}}) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Two fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ASCII clients
Section titled “ASCII clients”A telnet client that did not agree to UTF-8 is sent each box-drawing character as the nearest ASCII one:
- a light line as
- - a double or heavy line as
= - an upright as
| - a corner or tee as
+
A double box stays recognisably double, and colour is kept. A piece that is not box drawing at all, such as an emoji or the tee bracket round a title in the Unicode styles, becomes the ascii style’s piece instead. Tree guides become |- and `-. A "sep" between flex items is translated the same way. The text inside a layout is never changed. The portal ignores border characters and draws borders itself.
::: seealso
Layout Themes
Section titled “Layout Themes”A theme colours the parts of a layout: borders and gauge bars, titles, table headings, field labels, list bullets, tree guides, and the quiet lines between things. The text inside is left as it is written. A layout takes a theme from its "theme" option, and one that names none takes the game’s layout_theme option. Unset, layouts have no colour. A layout’s theme reaches every layout inside it, the way its "border" does.
> think box(Hi there,Sheet,30,{{"theme":"nord"}})+==========< Sheet >=========+| Hi there |+============================+That box is drawn in Nord’s blue, its title in Nord’s purple and bold. The examples in these topics show the text a client is sent, without its colour.
Kinds of theme
Section titled “Kinds of theme”A theme is one of these:
| Kind | Written |
|---|---|
| a name from THEMES() | "theme":"nord" |
| a genre | "theme":"fantasy" |
| a theme made from one colour | "theme":{"seed":"#7aa2f7","harmony":"triadic"} |
| a base16 scheme | "theme":{"base16":["#2e3440", ... sixteen colours]} |
| any of these with colours changed | "theme":{"preset":"nord","colors":{"primary":"#bf616a"}} |
terminal uses only the sixteen standard colours, so each player sees it in the colours their own client is set to. Hundreds of base16 schemes exist for editors and terminals.
Genres
Section titled “Genres”There is one genre for each MSSP genre: fantasy, historical, horror, modern, mystery, romance, science-fiction and spiritual (romance serves MSSP’s Adult as well). A genre theme changes the shapes as well as the colours, down to a box’s corners:
fantasy- double lines with diamond corners and fleurons either side of a title, and a gauge of solid and shaded blockshorror- a cracked heavy line with daggers at the cornersmystery- dashed lines with hollow diamondsromance- rounded lines with heartsscience-fiction- heavy lines with bracket corners, triangle bullets and a segmented gauge
A client without Unicode gets each in ASCII.
Themes from one colour
Section titled “Themes from one colour”The other colours take their hues from the seed, by "harmony":
monochrome- one hueanalogous- its neighbours (the default)complementary- the opposite huesplit- either side of the oppositetriadic- three evenly roundtetradic- four
Each is made lighter or darker until it stands out from the background: 3 to 1 for lines, 4.5 to 1 for text, by the measure the web accessibility guidelines use. "contrast" from 0 to 1 raises both toward 7 to 1. "mode":"light" makes it for a light background; dark is the default.
Colours
Section titled “Colours”The colours, which "colors" sets by name:
primary- borders, rules, gauge bars and table headingssecondary- titles and field labelstertiary- list bulletsmuted- tree guides, separators, the line under table headings, a gauge’s empty partsuccess,warning,error,info- BADGE() and NOTICE() use thesesurface- the background behind every second row of a striped table or field list ("stripe":true)background,foreground- what the others are measured against; neither is painted
A colour is "#rrggbb", a standard colour from 0 to 15, or both as {"rgb":"#88c0d0","slot":6}. null leaves one out.
"look" sets a theme’s shapes:
"border"- a border style from LAYOUT BORDERS"title"- the pieces either side of a title, joining it to the line:["< ", " >"]"guide"- a tree guide style, see TREE()"bullet"- the mark before each item of BULLETS()"gauge"- GAUGE()‘s pieces:["[", "#", "-", "]"], the opening, the filled part, the empty part and the close"separator"- after each label of FIELDS()"rule"- the line under DATATABLE()‘s headings
"theme":{"preset":"fantasy","look":{"bullet":"+"}} keeps the rest of fantasy’s look; "look":null drops it. A theme’s border wins over the game’s layout_border, and a layout’s own "border" wins over both. A client without Unicode gets the plain ASCII form of each piece.
> think box(bullets(Sword|Shield,|),Kit,20,{{"theme":"fantasy"}})+======< Kit >=====+| * Sword || * Shield |+==================+That is how a client without Unicode sees it. With Unicode the edges are double lines with a diamond at each corner, the title sits between two fleurons, and each bullet is a fleuron.
Sixteen-colour clients
Section titled “Sixteen-colour clients”Each colour carries the standard colour a client with only the sixteen is sent instead, picked by its hue, so a pale blue is sent as blue rather than the grey nearest it. A client without colour is sent the layout as it is; nothing in a layout depends on colour alone. A client without Unicode gets ASCII borders in the same colours.
Making a theme
Section titled “Making a theme”THEME() shows what a theme comes to, and SWATCH() shows whether each colour stands out enough. Each of these starts from something small and lets the generator do the rest.
From one colour, here a blue with triadic harmony. The seed becomes the primary colour and the others are spaced round it:
> think json_query(theme({{"seed":"#7aa2f7","harmony":"triadic"}}),get,colors,secondary,rgb)"#ffa099"The same theme for a light background. Each colour is darkened until it stands out from white:
> think json_query(theme({{"seed":"#7aa2f7","harmony":"triadic","mode":"light"}}),get,colors,primary,rgb)"#4568b8"With "contrast":1, every colour stands out at least 7 to 1. The quiet muted colour moves furthest: without it, it is #6b615d at 3.1 to 1.
> think swatch({{"seed":"#d08770","harmony":"split","contrast":1}},64)Role Sample Colour 16-colour Contrast-------------------------------------------------------background - #190f0b 0 black -surface Sample #2b1c18 8 bright black -foreground Sample #e8dbd7 7 white 13.9:1primary Sample #d98f78 9 bright red 7.3:1secondary Sample #6cd2c9 14 bright cyan 10.5:1tertiary Sample #7fb7ec 12 bright blue 8.9:1muted Sample #a69c97 7 white 7.0:1success Sample #9abd53 10 bright green 8.8:1warning Sample #ee9748 11 bright yellow 8.2:1error Sample #f98b6e 9 bright red 8.0:1info Sample #86acff 12 bright blue 8.4:1From a base16 scheme, copied from an editor or terminal theme. Its sixteen colours go in order, base00 to base0F; base0D, the scheme’s blue, becomes the primary colour:
> think json_query(theme({{"base16":\["#1d1f21","#282a2e","#373b41","#969896","#b4b7b4","#c5c8c6","#e0e0e0","#ffffff","#cc6666","#de935f","#f0c674","#b5bd68","#8abeb7","#81a2be","#b294bb","#a3685a"]}}),get,colors,primary,rgb)"#81a2be"From a built-in theme with one colour changed:
> think json_query(theme({{"preset":"nord","colors":{"primary":"#bf616a"}}}),get,colors,primary,rgb)"#bf616a"From a genre with its look changed. This keeps fantasy’s colours and gauge but draws ASCII lines, a dash for each bullet and round brackets about the title:
> think box(bullets(Sword|Shield,|),Kit,20,{{"theme":{"preset":"fantasy","look":{"bullet":"-","border":"ascii","title":\["( "," )"]}}}})+------( Kit )-----+| - Sword || - Shield |+------------------+A gauge drawn in pieces of your own, in the reader’s own sixteen colours:
> think gauge(7,10,HP,{{"width":30,"theme":{"preset":"terminal","look":{"gauge":\["<","=",".",">"]}}}})HP <===============......> 70%A theme made once can be used again without making it each time. THEME() writes it out, and JSON() puts it into the options. To keep it, store it in an attribute as THEME() shows:
> think [setq(0,theme({{"seed":"#d08770","harmony":"split"}}))][box(Hello,,20,json(object,theme,%q0))]+==================+| Hello |+==================+Using themes
Section titled “Using themes”Written straight into options, a theme object goes inside them as it is: box(Hi,,30,{{"theme":{"seed":"#d08770"}}}). Each player can also pick a theme of their own for every layout they read with @THEME. THEME() writes any theme out in full, to keep in an attribute, and SWATCH() shows one’s colours and how well each stands out.
::: seealso
themes()
Section titled “themes()”themes()
The names of the themes the game offers, for the "theme" layout option, @THEME and the layout_theme game option: the built-in ones staff have not disabled, then those staff added (see @THEME/LIST).
The built-in ones are terminal, which uses the sixteen standard colours each client draws in its own palette; one for each genre a game can name in its MSSP settings, each with its own border, title ornaments, bullet and gauge as well as colours (see LAYOUT THEMES): fantasy historical horror modern mystery romance science-fiction spiritual; and well-known colour schemes: catppuccin-mocha catppuccin-latte dracula gruvbox-dark nord solarized-dark solarized-light tokyo-night.
Example
Section titled “Example”> think first(themes())terminal::: seealso
theme()
Section titled “theme()”theme(<theme>)
Writes "theme" option takes; see LAYOUT THEMES. A theme made from a seed colour can be made once and kept:
think set(me,MYTHEME:[theme({{"seed":"#d08770","harmony":"split"}})])think box(Hello,,30,json(object,theme,v(MYTHEME)))&MYTHEME me=... would keep the [theme(...)] call as written instead of the theme it makes. SET() tells you the attribute was set, as @set does.
The result is read back as it is, so it can be edited and passed on.
Example
Section titled “Example”> think json_query(theme(nord),get,colors,primary,rgb)"#81a1c1"::: seealso
swatch()
Section titled “swatch()”swatch(<theme>[, <width>])
A table of client, since it looks however the reader’s client draws it. The table is drawn in the theme.
Example
Section titled “Example”> think swatch(nord,64)Role Sample Colour 16-colour Contrast--------------------------------------------------------------background - #2e3440 0 black -surface Sample #3b4252 8 bright black -foreground Sample #e5e9f0 7 white 10.3:1primary Sample #81a1c1 4 blue 4.6:1secondary Sample #b48ead 5 magenta 4.4:1 (needs 4.5)tertiary Sample #88c0d0 6 cyan 6.2:1muted Sample #4c566a 8 bright black 1.7:1 (needs 3)success Sample #a3be8c 2 green 6.1:1warning Sample #ebcb8b 3 yellow 8.0:1error Sample #bf616a 1 red 3.1:1 (needs 4.5)info Sample #88c0d0 6 cyan 6.2:1::: seealso
@theme
Section titled “@theme”@theme[/light|/dark] <object>=<theme>@theme <object>=@theme/refresh [<player>]
Sets the theme every layout is drawn in for a player: boxes, tables, gauges and the rest, from any function in LAYOUT FUNCTIONS. "theme" option takes (see LAYOUT THEMES): a name from THEMES(), or a theme written out as JSON inside a second pair of braces, as for a layout function’s options: @theme me={{"seed":"#7aa2f7","harmony":"triadic"}}. With nothing after the =, the theme is cleared.
Output: none.
Light and dark
Section titled “Light and dark”/light makes the theme for a client with a light background, and /dark for a dark one. A theme made from one colour, which includes the genre themes, is made again for that background; a well-known scheme such as nord stays as it is. Code given with /light or /dark is worked out first, and the theme it gives is kept, not the code. Code that works out to nothing is refused with them, since there is no theme to keep.
Parents and the player ancestor
Section titled “Parents and the player ancestor”The theme is kept in the THEME attribute, and a player without one of their own takes the one their parent has, or their parent’s parent, then the player ancestor’s (see ANCESTORS). Set it on the object a group of players is parented to, a faction for example, and each of them gets it.