Mana Fields · build 0.3.6 · 02 November 2026

Visual design breakdown

What is on the screen, what draws it, in what order, and which of it a designer can change on their own. Every picture is taken from the game itself; every number in a table is read out of the code when this document is built.

Download PDFGame overview
The battle screen in the middle of a game
In this document
  1. How to read it: three kinds of knob
  2. The screen and its layers
  3. The board, layer by layer: ground, wires, buildings, gems, units
  4. Aiming, placing, effects and motion
  5. The rail, cards and sheets
  6. Palette, art and type
  7. Where to change what
1

How to read this

The game has almost no pictures in it. Apart from the logo, everything is drawn by code from flat colour, simple shapes and a set of single-colour SVG icons. So "changing the art" means one of three different things.

art

Replace a file in art/. Every icon is a one-colour SVG: the game tints it at draw time, so only its shape counts and its own colours are thrown away. Keep the file name and a square canvas.

token

Change a number or a colour at the top of a script, or in config.json. Each is named, commented and listed in this document with its file and line. No logic changes: open the file, change the value, relaunch.

code

The shape or the behaviour itself: a chevron instead of a box, what is drawn first, when something animates. Needs a programmer, but the file and function are named here so the request can be exact.

Sizes are in cells. Almost every length on the board is a fraction of one board cell, so the whole screen scales with the window. In the screenshots here the window is 720×1272 and a cell is about 89 px. Colours are written as hex; Color(r, g, b, a) in code is the same colour with each channel from 0 to 1.

2

The screen and its layers

The battle screen is two blocks side by side: the board on its dark slab, and the rail beside it with both players' keeps and the spellbook of the player to move.

Anatomy

scenes/battle.tscn · scripts/battle.gd · _layout()
Battle screen with numbered parts
  1. 1Top keep: the opponent's health and mana pools. side_panel.gd · _draw_keep
  2. 2Menu: the game's logo as a button. _draw_menu · paint_logo
  3. 3Spellbook: one card per spell, with a bar of how much of its price is banked. paint_card
  4. 4Turn: the end-turn cross with four action diamonds round it. _draw_turn · _draw_ap
  5. 5Bottom keep: the player at the bottom. _draw_keep
  6. 6Board slab: the dark plate the board sits on. battle.gd · SLAB
  7. 7Frontier: where each player's ground ends. frontier.gd
  8. 8Charged wire: a row holding charge, close to its cap, so its ends glow. wires_view.gd
  9. 9E-Shield: two pads and the discharge between them. structures_view.gd
  10. 10Unit: a diamond in its owner's colour, filled as far as it has health. unit_marker.gd
  11. 11Gem: one mana tile. gem.gd

The stack

z_index in board_view.gd · setup() and battle.gd

Each layer is a separate node. Higher draws over lower. The six layers of the board itself are pulled apart in the picture, each taken from the game with everything else hidden; their numbers are marked in the table. The rest only appear while something happens, or lie off the board.

zLayer
0Board slab battle.gd
1Frontier and ground wash frontier.gd
2Where a drag may go, washed target_hint.gd
9Wires, charge, bolts wires_view.gd
9Buildings: shields, towers structures_view.gd
10Gems gem.gd
12Units unit_marker.gd
15A unit or gem under the finger
17A building being aimed at, lifted in gold
zLayer
18Aim halos and info plates target_hint.gd
19A unit being placed place_ghost.gd
20Effects: sparks, shards, numbers, flying mana
50Rail side_panel.gd
55Screen dim
60Card under the finger, card sheet
65Banner
70–80Menu, options, help, result

Each layer alone

docs/design_capture.gd

The same moment of the same game, one layer at a time, on a checkerboard where the layer is see-through. Wires and the frontier wash are faint on purpose: they sit under the gems and read only through the gaps.

Slab · z 0
Frontier · z 1
Wires · z 9
Buildings · z 9
Gems · z 10
Units · z 12

Built up

The same layers added one at a time, bottom first. The last picture is the board as the player sees it.

3

The board

Six columns by fourteen rows. Gems sit in the cells; units, wires and shield pads sit on the lines and nodes between them. Everything below is drawn in that order.

Board slab

battle.gd · _layout() · Kit.plate
The slab's top edge, with the opponent's ground washed red over it.

A flat plate, SLAB, with the grey frame every open sheet wears (Kit.SHEET_EDGE), rounded by Kit.ROUND. It is the board's size plus its frame (Kit.BOARD_INSET), so the outer gems run up to the frame. Nothing is drawn on it: the grid and the colour you see on the board come from the layers above.

token colour and frame. code a texture or pattern on the slab: the slab draws flat colour only.

ConstantValueWhat it setsWhere
SLAB#080c16The board's backing panel: a shade off the window behind it, and inset by the one measure so the outermost gems are not cut flush with its frame.scripts/battle.gd:82
SHADErgba(15,13,10,0.55)What darkens the board under anything standing over it: enough to put the sheet in front, not so much that the board is gone.scripts/battle.gd:85
RAIL1.0Cells of width the rail down the right of the board keeps for itself. It is narrow on purpose: whatever the board does not need goes to the board.scripts/battle.gd:88
STRIP3.0Cells of width the rail takes while it is open over the board, where a card has room for its cost and a keep for its counts.scripts/battle.gd:91

Frontier and ground

scripts/frontier.gd
Both frontiers meeting: each moved half its width onto its own side, so they lie side by side.

Each player's ground is washed in their colour at WASH strength, and its edge is drawn as a barrier: a 1 px rail down either side and solid pieces between, two to a cell, the second player's set half a piece behind the first's. Where the edge meets the board's rim it carries on to the frame, which takes the owner's colour beside that ground.

The colours are the players' own, Kit.ZONE, which a player picks before a campaign from Kit.ZONE_CHOICES.

ConstantValueWhat it setsWhere
BAR6.0How thick the barrier runs and each rail of it, in pixels, rails set on whole pixels so they are never blurred; and how long a piece is, and each gap, in cells.scripts/frontier.gd:22
RAIL1.0same as abovescripts/frontier.gd:23
PIECE0.25same as abovescripts/frontier.gd:24
WASH0.15How strongly the ground each player holds is washed in their colour: barely.scripts/frontier.gd:26

token thickness, dash length, wash. code a different line style, such as a glow or a dotted edge.

Wires and charge

scripts/wires_view.gd

A wire runs through the middle of every row and every column, with a node where they cross. They are drawn under the gems, so they read only in the gaps between them. A row that holds charge carries a live discharge along its wire.

Charged. The colours on the row share its length; how much charge there is sets how far it throws, how bright and how thick it is.
Torn. A dead stretch under each gem, in TORN, cut where the gaps show.
charge on a row / wireCap calm≥ WARN: beats 5 left: amber blink3 left: red, fasterat cap: overloads BLINK: glow at both ends of the wire
STEP: one zigzag JITTER × charge halo: 2× thick, 35 % re-rolled FLICKER times a second
ConstantValueWhat it setsWhere
GRIDrgba(89,230,255,0.1)The conductors themselves, and the thickness of every line, in cells.scripts/wires_view.gd:17
NODErgba(89,230,255,0.18)same as abovescripts/wires_view.gd:18
THICK0.028same as abovescripts/wires_view.gd:19
TORNrgba(255,46,99,0.6)A torn wire: the dead ends left behind, and how wide the gap between them runs, in cells. It is severed between every two gems of its row, where the gap shows, rather than once under the middle of the board.scripts/wires_view.gd:23
GAP0.12same as abovescripts/wires_view.gd:24
TEAR0.55The wire tearing: how long it takes, how far past its resting width each gap is thrown open before the ends settle, and the sparks thrown off every cut -- how many, and how far they fly, in cells.scripts/wires_view.gd:28
RECOIL2.4same as abovescripts/wires_view.gd:29
SPARKS5same as abovescripts/wires_view.gd:30
SPARK_REACH0.42same as abovescripts/wires_view.gd:31
LIVE_THICK0.015The discharge: how thick, how far it dances off its wire, and how long one zigzag segment runs -- all in cells.scripts/wires_view.gd:34
JITTER0.032same as abovescripts/wires_view.gd:35
STEP0.17same as abovescripts/wires_view.gd:36
FLICKER18.0How many times a second an arc re-rolls its shape.scripts/wires_view.gd:38
BOLT_SPEED26.0How fast a released bolt travels, in cells per second.scripts/wires_view.gd:40
HEM0.16How far a row stops short of the board's rim, in cells.scripts/wires_view.gd:42
FLASH0.22How long the white flash of a release lasts.scripts/wires_view.gd:44
WARN0.7How full a wire has to be before it beats.scripts/wires_view.gd:46
BLINK[[3, 3.0, Color(1, 0.2, 0.2)], [5, 1.0, Color(1, 0.85, 0.2)]]The warning that a row is about to let go into whatever stands on it: a glow at either end of its wire, under the gems, blinking once the row is within so many units of its cap, at so many blinks a second, in this colour. The closer, the faster.scripts/wires_view.gd:51
DOT0.2How far the glow reaches from the end of the wire, in cells: it is full at the middle and gone at this far.scripts/wires_view.gd:54

token every colour, thickness, speed and blink step above. code the zigzag itself, or a sprite instead of drawn lines.

Buildings: E-Shield and towers

scripts/structures_view.gd
A charged E-Shield: a pad at each end, the discharge between them.

Pads are chevrons pointing in, flat on the outer side, drawn under the gems so only their corners show. A pad is filled from the bottom as far as the shield has health; the rest is its owner's colour darkened (Kit.unit_worn). A hairline in the interface ink runs just inside its outline.

The discharge burns as hard as the two rows either side hold charge. With none, it is a white dashed hairline that flickers. A blow it puts out makes it flash white and throw a ring.

PAD × PAD_TALL ARM: point depth BEAM, THROW, STEP fill = health
ConstantValueWhat it setsWhere
RIM0.45How wide the rim is that a card let go on is aimed at a building, in cells.scripts/structures_view.gd:10
PAD0.65How wide and how tall a shield's chevron is, in cells, how deep its point runs, and how close a finger has to come to it to aim at it.scripts/structures_view.gd:13
PAD_TALL0.6same as abovescripts/structures_view.gd:14
ARM0.25same as abovescripts/structures_view.gd:15
REACH0.4same as abovescripts/structures_view.gd:16
SOFT0.04How far back from each of its corners a shield's chevron is rounded, in cells: its shoulders and the two corners of its flat back, and its point.scripts/structures_view.gd:19
POINT0.1same as abovescripts/structures_view.gd:20
BEAM0.09A shield's discharge: how thick it runs with a full pair of rows behind it, how far it throws off its line, how long each stretch of it is, all in cells, and how many times a second it is re-rolled.scripts/structures_view.gd:24
THROW0.07same as abovescripts/structures_view.gd:25
STEP0.17same as abovescripts/structures_view.gd:26
FLICKER18.0same as abovescripts/structures_view.gd:27
IDLE_LINE1.5How wide an uncharged shield's hairline is drawn, in pixels: white and plain enough to see on the board.scripts/structures_view.gd:30
IDLE_JIG0.2How far its dashes shift each time it flickers, as a share of a dash.scripts/structures_view.gd:32
DASH0.08How long each dash of that hairline is, in cells.scripts/structures_view.gd:34
SOAK0.38A blow put out: how long the shield burns white for it, in seconds, and how wide the ring it throws up where the blow met it gets, in cells.scripts/structures_view.gd:37
RING0.55same as abovescripts/structures_view.gd:38

token sizes, rounding, beam. code the outline itself, which is a list of points in _pad_outline() and SHAPE.

Gems

scripts/gem.gd · config.json tileColors
One gem, close up.
OUTLINEblack, 1.1× GLOSSwhite 17 %, 0.82× MARK0.44, CARVED BODY: 0.88 cell, flat tone

Every gem is the same diamond (art/i-diamond.svg) in four stacked pieces: a black outline behind, the body in the colour's flat tone, a smaller white gloss nudged up, and the colour's mark cut into the middle so colour-blind players can tell them apart. Gems the player to move cannot swap are turned down to OUT_OF_REACH; while a spell picks its target all of them go to DIMMED. A gem in a match flashes white and grows a little.

The player to move's own ground: full colour.
The other side: OUT_OF_REACH, the frontier wash showing through.
ConstantValueWhat it setsWhere
GLOSS0.17The highlight, as flat white over the gem's own colour.scripts/gem.gd:10
OUTLINE0.1The outline, as a fraction of the diamond it sits behind.scripts/gem.gd:12
BODY0.88How wide the diamond is, as a fraction of its cell, and how wide the mark inside it is, as a fraction of the diamond.scripts/gem.gd:15
MARK0.44same as abovescripts/gem.gd:16
CARVEDrgba(8,13,23,0.8)The mark is cut into the gem rather than laid on it.scripts/gem.gd:18
DIAMONDpreload("res://art/i-diamond.svg")same as abovescripts/gem.gd:19
ConstantValueWhat it setsWhere
DIMMEDrgba(128,128,128,1)How far the gems darken while a placement or a spell is picking its cell. They are turned down, not made see-through: nothing under a gem shows.scripts/board_view.gd:10
OUT_OF_REACHrgba(107,107,107,1)How far a gem darkens when it is not the player's to touch: swaps only happen on ground you hold, so ground you do not hold is turned down.scripts/board_view.gd:13

art i-diamond.svg (the body) and each colour's mark, i-*.svg and g-triangle.svg. token the colours in config.json and the proportions above. code a gradient, a shadow or a full-colour gem bitmap.

Units

scripts/unit_marker.gd · Kit.diamond
A unit at full health.
POINT_ROUND health lostdarkened 0.5 health leftZONE[owner] glyph u-*.svg · hairline BORDER

A unit stands on the nodes between gems. Its body is a rounded diamond the size of a cell, in its owner's colour (not its race's): the share of health it has left fills it from the bottom. Its glyph and a hairline are in the interface ink, gold while a spell is aimed at it. The same body, lighter, is the ghost shown while a unit is being placed and the card carried under the finger.

ConstantValueWhat it setsWhere
DISC1.0How wide the disc is, as a fraction of a cell.scripts/unit_marker.gd:11
BORDER0.035How thick the hairline runs, in cells.scripts/unit_marker.gd:13
DOT0.05A dot on a corner: how big, as a fraction of a cell, and how far out towards the corner it stands, as a fraction of the way.scripts/unit_marker.gd:16
DOT_OUT0.6same as abovescripts/unit_marker.gd:17
STEP0.12The burning border: how long one zigzag runs, how far it dances off its line and how thick it burns, in cells, how many times a second it re-rolls, and how many times a second it beats with little charge and with a lot.scripts/unit_marker.gd:21
THROW0.035same as abovescripts/unit_marker.gd:22
BEAM0.065same as abovescripts/unit_marker.gd:23
FLICKER18.0same as abovescripts/unit_marker.gd:24
BEAT_SLOW0.7same as abovescripts/unit_marker.gd:25
BEAT_FAST4.0same as abovescripts/unit_marker.gd:26
ConstantValueWhat it setsWhere
POINT_ROUND0.22How far a unit's diamond is rounded at each point, as a fraction of its radius, and how many stretches each rounded point is drawn in.scripts/ui_kit.gd:135
POINT_STEPS6same as abovescripts/ui_kit.gd:136

art u-imp.svg (Ranger), u-tower.svg (Tower), u-bomb.svg (Bombarder). The Tower is two diamonds with a small diamond between them (Kit.unit_blocks, Kit.PLATE). token size, hairline, rounding. code another body shape, or bars instead of the fill.

4

Aiming, placing and motion

What the board shows while a card is in the player's hand, and how it moves when something happens.

Aiming a spell

scripts/target_hint.gd
A Strike carried over an enemy unit
Aiming. A Strike carried over the enemy: every gem dims, what it may hit is washed in gold, what it is over now is ringed.
A Ranger being placed
Placing. The unit it will be, lighter, on the node under the finger. Red dots are the nodes it would strike from there.
ConstantValueWhat it setsWhere
EDGE#ffd60ascripts/target_hint.gd:9
FAINT_FILL0.12How far back the ground a drag may go to is washed. It is a hint, and there is a lot of it at once.scripts/target_hint.gd:41
AIM_FILL0.85How strongly the rows a spell is aimed at are washed inside their frame.scripts/target_hint.gd:43
PLATE_UP0.78A plate over something picked out: how far over its middle it stands, how tall its writing is, and how much room is kept round the writing, in cells.scripts/target_hint.gd:46
PLATE_TEXT0.3same as abovescripts/target_hint.gd:47
PLATE_PAD0.1same as abovescripts/target_hint.gd:48
MARK0.34How big the spell's mark is on each cell it is aimed at, drawn in the carved ink a filled card's mark is, as a share of a cell.scripts/target_hint.gd:51
HALO_POINT0.04The edge of a block of cells aimed at runs parallel to the edges of its gems: a point at the middle of every cell's side, this far out of the block, and a notch between every two, this far in, both in cells, the two adding up to half a cell. And how far back from a point and from a notch the edge starts to round.scripts/target_hint.gd:56
HALO_NOTCH0.46same as abovescripts/target_hint.gd:57
HALO_POINT_ROUND0.12same as abovescripts/target_hint.gd:58
HALO_NOTCH_ROUND0.2same as abovescripts/target_hint.gd:59
HALO_STEPS8How many stretches a rounded corner of it is drawn in.scripts/target_hint.gd:61
REACH0.22The wash over ground a picked unit lays its influence on.scripts/target_hint.gd:63
ConstantValueWhat it setsWhere
DISC1.0The same disc the unit marker draws, and the same hairline of health round it. The numbers are repeated rather than read off that script: everything on the board is loaded by path, so a fresh checkout has no class to read them from.scripts/place_ghost.gd:11
BORDER0.035same as abovescripts/place_ghost.gd:12

The ghost repeats the unit's DISC and BORDER rather than reading them from unit_marker.gd. Change both together.

Effects and motion

board_view.gd · apply() · config.json timing

Every animation replays an event the rules already decided, so none of them can be skipped or reordered by art alone. The durations live in config.json, in milliseconds:

timing.swap190 ms
timing.matchHighlight150 ms
timing.remove220 ms
timing.mana220 ms
timing.gravity240 ms
timing.projectile340 ms
timing.impact220 ms
timing.unitMove240 ms
timing.step90 ms

What each effect is

  • spark: a disc that grows 1.9× and fades, on every hit and heal.
  • burst: 8 triangle shards thrown out, spinning, 0.34–0.5 s, when something is destroyed.
  • float text: the number, 0.4 cell high, rising 0.9 cell over 0.6 s.
  • mana mote: the colour's mark flying to the keep, shrinking to 45 %.
  • bolt: lightning running down a column at BOLT_SPEED.
  • bomb: u-bomb.svg lobbed, swelling LOB_SWELL× at the top of its arc.

Effect colours

damage#ff2e63
heal#2bff88
shield#9df3ff

These are written into board_view.gd · apply() where each event is handled, not as named constants. Changing them is a small code change.

ConstantValueWhat it setsWhere
LOB_SIZE0.4How big a lobbed bomb is drawn, in cells, and how many times that it swells to at the top of its flight.scripts/board_view.gd:16
LOB_SWELL1.8same as abovescripts/board_view.gd:17
SHUFFLE420How long the gems take to fly to where a shuffle left them, in milliseconds.scripts/board_view.gd:863
5

The rail, cards and sheets

Everything off the board is drawn by side_panel.gd, detail_panel.gd and the sheets in battle.gd, from the plates and buttons in ui_kit.gd.

The rail

scripts/side_panel.gd · _draw()
The rail, narrow: its top and its foot. On a wide window it is pinned open at three cells, with names on the cards.

Drawn in this order

  1. The backing, Kit.BACK.
  2. The book's recess, BOOK, round the menu, the cards and the turn button.
  3. The cards. Then the tick and cross over a card that waits to be confirmed.
  4. Both keeps: a health wall at the outer end, then a mana pool per colour, each with a little live wire as long as that pool.
  5. The menu button (the logo) and the turn button.
  6. A frame round menu, book and turn in the colour of the player to move: the one place the rail says whose turn it is.

A card's faces

  • CARD normally; TINT_LIT and a little smaller while pressed.
  • Kit.TINT_DIM with DIM_INK when it cannot be cast.
  • Framed in GOLD while armed; filled with GOLD and written dark when chosen.
  • The bar along its foot: one segment per colour of its price, filled as far as the pool covers it.
ConstantValueWhat it setsWhere
HOLD0.28How long a press has to last before it picks the card up: framed in gold, and a pull any way, along the column too, carries it out rather than scrolling the book. Let go where it is, it opens as a tap would.scripts/side_panel.gd:32
SLOP0.12Movement that turns a press into a scroll or a drag, in cells.scripts/side_panel.gd:34
SWIPE0.3Movement across the rail that opens it or shuts it, in cells.scripts/side_panel.gd:36
SLIDE0.14How long the rail takes to slide the whole way open or shut.scripts/side_panel.gd:38
BLINK_AT0.25The clock blinks once it is down to this much of the turn.scripts/side_panel.gd:40
MANA_FULL20.0What runs a pool's discharge the whole length of its wire. Mana is not capped: once a pool is past it, the fullest pool in the keep is what the others are drawn against instead, so none of them reads full while another holds more.scripts/side_panel.gd:45
WIRErgba(89,230,255,0.1)A pool is drawn the way a row's charge is on the board: a wire, and a discharge dancing along it. The measures are in cells -- how thick the wire and the discharge run, how far it dances off the wire, how long one zigzag runs -- and how many times a second it re-rolls its shape. A pool's line is a fraction of a row, so its discharge runs thinner than the board's and is thrown less far.scripts/side_panel.gd:52
WIRE_THICK0.028same as abovescripts/side_panel.gd:53
ARC_THICK0.02same as abovescripts/side_panel.gd:54
ARC_JITTER0.014same as abovescripts/side_panel.gd:55
ARC_STEP0.17same as abovescripts/side_panel.gd:56
ARC_FLICKER18.0same as abovescripts/side_panel.gd:57
ARC_SHADE0.4How far a point of the discharge may be lit or darkened off its own colour.scripts/side_panel.gd:59
INK#d8f6ffscripts/side_panel.gd:61
DIM_INK#4d6478scripts/side_panel.gd:62
GOLD#ffd60ascripts/side_panel.gd:63
BADGE_BGrgba(5,8,15,0.85)scripts/side_panel.gd:64
CARVED#060a12Marks cut into the wood rather than laid on it: the cross and the menu bars.scripts/side_panel.gd:66
BACKKit.BACKThe column stands on its own backing, and that is what shows between the panels and between the column and the board.scripts/side_panel.gd:69
BOOK#121a2bThe recess a book of cards sits in, and the card standing in it.scripts/side_panel.gd:71
CARD#1e2c48same as abovescripts/side_panel.gd:72
TINTKit.TINTsame as abovescripts/side_panel.gd:73
TINT_LITKit.TINT_LITsame as abovescripts/side_panel.gd:74
GLYPH0.99How much of a button's height its mark takes: the picture on a card, the cross on the turn panel.scripts/side_panel.gd:275
BANK0.26The strip at the foot of a card that shows what is banked towards it, as a fraction of the card's height.scripts/side_panel.gd:278
HEAD0.78The part of an open card that holds its mark with its name and its price beside it, as a fraction of the card's height. What is banked towards it takes the rest, under it.scripts/side_panel.gd:282
OPEN_TALL1.35How much taller every button stands on the open rail: a card over what its mark alone asks for, the menu and the turn button over their narrow selves.scripts/side_panel.gd:285
TEXT_GAP0.16The air between a button's mark and what is written beside it, in cells.scripts/side_panel.gd:287
SPENT#7d8fa3A place for an action the turn does not have: seen, and plainly not gold.scripts/side_panel.gd:289
CROSS0.6How far one arm of the end-turn cross reaches from its middle, as a share of half a card's mark.scripts/side_panel.gd:292
AP_FAR0.96The action diamonds round the end-turn cross, as fractions of one arm of the cross: how far each stands from its middle, and how big it is from its centre to a point. What is left between the two is the air off the cross.scripts/side_panel.gd:296
AP_SIZE0.58same as abovescripts/side_panel.gd:297
SLOT0.72The panels that hold one thing -- the turn, the menu -- are the same height, so the rail reads as one stack of equal plates.scripts/side_panel.gd:344
WALL0.5A keep is cut to exactly what it holds, one thing under the other: the wall its health is drawn as, and a line for every pool. Whatever it does not need goes to the books, which is where a rail that is mostly cards wants its height.scripts/side_panel.gd:349
POOL0.16same as abovescripts/side_panel.gd:350
COST_STEP2.1What a card costs: the mark of the mana it is paid in and the price beside it, on a badge `h` tall, dark unless `back` says otherwise. How far one price stands from the next on a card paid in more than one colour, in badge heights.scripts/side_panel.gd:1028
BADGE_W1.9How wide a price badge is, in badge heights.scripts/side_panel.gd:1030

The rail repeats several interface colours instead of reading them from Kit: INK, GOLD, CARVED, and BOOK / CARD, which equal Kit.TINT / Kit.TINT_LIT. The same is true of detail_panel.gd, help_panel.gd, drag_card.gd and lobby.gd. A palette change has to be made in each, or the copies merged into Kit first, which is a small code task.

The card sheet

scripts/detail_panel.gd
A card held open: picture, name, price, then the rules text with its own icons inline.

A plate with the sheet frame, over a dimmed screen. The picture is the spell's or unit's icon, ART cells high; a keep shows its race's crest instead, a hexagon in the race's colour. The rules text comes from config.json: a word in braces such as {t-damage} is drawn as that icon, at the size of the text.

art icons. token sizes and colours below, and the text in config.json. code the layout of the sheet.

ConstantValueWhat it setsWhere
PAD0.26inside the sheet, in cellsscripts/detail_panel.gd:8
ART1.8the picture, in cellsscripts/detail_panel.gd:9
LINE0.5a line of body text, in cellsscripts/detail_panel.gd:10
INK#d8f6ffscripts/detail_panel.gd:12
DIM#6f8aa8scripts/detail_panel.gd:13
GOLD#ffd60ascripts/detail_panel.gd:14
RULE#1b3450scripts/detail_panel.gd:15
BADGE0.56the cost under a card's name, in cellsscripts/detail_panel.gd:16
WALL0.5a player's health, in cellsscripts/detail_panel.gd:17
POOL0.4a line of a player's mana, in cellsscripts/detail_panel.gd:18
CARVED#060a12scripts/detail_panel.gd:19

Sheets, buttons and the banner

ui_kit.gd · sheet_button · battle.gd · _popup
The menu: one sheet, sheet buttons.

Every question the game asks is the same sheet: a Kit.TINT plate framed in Kit.SHEET_EDGE, over the screen dimmed by SHADE. Every button on it is cut from the window, not in pixels: about 72 % of its width and 7 % of its height.

Button statePlateFrameText
normalTINTSHEET_EDGEINK
hoverCARVEDGOLDGOLD
pressedGOLDGOLDCARVED
disabledTINT_DIMSHEET_EDGEINK_DIM

A pressed button dips to 95 % in 0.09 s. The banner ("Red to move") is a sheet without buttons; it stays 0.8 s for a turn and 1.6 s otherwise, then goes without a fade.

ConstantValueWhat it setsWhere
ROUND6Everything in the interface is rounded a little, and by the same measure: a panel and the frame round it by one, a button inside it by a tighter one.scripts/ui_kit.gd:12
ROUND_TIGHT4same as abovescripts/ui_kit.gd:13
SPACE0.12One measure spaces the whole interface -- the gap between two panels and the inset inside one are the same fraction of a board cell, everywhere.scripts/ui_kit.gd:17
TEXT_MIN0.36The smallest writing the interface sets, as a fraction of a cell. A board of fourteen rows on a phone leaves a cell of about forty pixels, and a quarter of that is not writing anybody reads: what is written is sized off this floor as well as off the cell it stands in.scripts/ui_kit.gd:28
FRAME2.0The frame round every panel: the same thickness wherever it is drawn, in the player's own colour when the panel is theirs to use and in `EDGE` when it is not.scripts/ui_kit.gd:102
SHEET_TEXT0.34How tall a sheet's button is written, as a fraction of its height.scripts/ui_kit.gd:267
6

Palette, art and type

Neon on near-black: plates are unlit circuitry, the ink is a pale cyan glow, and the one accent is an electric amber.

Palette

read from scripts/ui_kit.gd and config.json

Interface tokens scripts/ui_kit.gd

Kit.TINT#121a2b
Kit.TINT_LIT#1e2c48
Kit.TINT_DIM#0b1120
Kit.INK#d8f6ff
Kit.INK_DIM#6f8aa8
Kit.GOLD#ffd60a
Kit.ICE#6fcbffThe colour of what Frost lays over a row it holds.
Kit.CARVED#060a12The ink drawn over gold: near black.
Kit.EDGE#1b2740The frame round a panel that belongs to nobody in particular: a shade above the backing the whole interface stands on.
Kit.BACK#0b1220The frame round a panel that belongs to nobody in particular: a shade above the backing the whole interface stands on.
Kit.SHEET_EDGE#3a4354The frame round whatever stands open over the board -- a sheet, a banner, a card held open -- and round every button: one dark grey, whoever is to move.

Player colours Kit.ZONE_CHOICES

#4a7fc2
#e04444

Mana colours config.json · tileColors[] · light / flat / mid / dark

Plasmared mana
#ff2e63
Ionblue mana
#00e5ff
Fluxgreen mana
#2bff88
Photongold mana
#ffd60a
Hexviolet mana
#b14aff

token all of them. A mana colour has four tones: flat is the gem and everything on the board, light, mid and dark are kept for the interface.

Art

art/*.svg

Every icon the game has, as the game draws it: tinted one colour. The names say where they go: i- a mana colour's mark, g- a mark redrawn to sit in a gem, s- a spell, u- a unit, t- a term in the rules text.

g-triangle.svgFlux mark inside a gem (board, logo, help)
i-circle.svgIon mark
i-diamond.svgPhoton mark, the body of every gem
i-pentagon.svgHex mark
i-square.svgPlasma mark
i-triangle.svgFlux mark
s-bolt.svgspell Charge
s-bullet.svgspell Killer
s-crosshair.svgspell Strike
s-fence.svgnot drawn in the game
s-frost.svgspell Frost
s-hammer.svgspell Hammer
s-hand.svgspell Collect
s-ion.svgspell Storm
s-node-shield.svgspell Load
s-shield.svgspell Armor
s-splice.svgspell Heal
s-tape.svgspell Rewire
s-tower-bolt.svgspell E-Shield
t-ap.svginline in rule text {t-ap}
t-board.svginline in rule text {t-board}
t-charge.svginline in rule text {t-charge}
t-column.svgnot drawn in the game
t-damage.svginline in rule text {t-damage}
t-hp.svginline in rule text {t-hp}
t-influence.svginterface
t-melee.svgnot drawn in the game
t-move.svginline in rule text {t-move}
t-node.svgnot drawn in the game
t-range.svgnot drawn in the game
t-ranged.svgnot drawn in the game
t-row.svginline in rule text {t-row}
t-time.svgnot drawn in the game
u-bomb.svgunit Bombarder
u-imp.svgunit Ranger
u-mage.svgnot drawn in the game
u-tower.svgunit Tower
u-treant.svgnot drawn in the game

art replace any of them in place. They are drawn at many sizes, from 0.3 of a cell in the text to 1.8 cells on a card, so they should read at 16 px. Fine lines and inner detail will be lost; a solid silhouette is what works.

Type

The game ships no typeface. Everything in it is set in Godot's built-in default font, at sizes taken from the cell (never under TEXT_MIN of a cell). The faces in this document (Unbounded, Golos Text, JetBrains Mono) are the documents' own and are not in the game.

code a typeface for the game: a font file, loaded once into Kit.theme() and used wherever the code now asks for the fallback font.

7

Where to change what

Redraw an icon, a unit's glyph or a spell's picture
Replace the SVG of the same name in art/. One colour, square, a solid shape.
art
Recolour a mana type
config.json · tileColors[]: flat, light, mid, dark.
token
Recolour the interface
ui_kit.gd, and the copies in side_panel.gd, detail_panel.gd, help_panel.gd, drag_card.gd, lobby.gd.
token
Change the players' colours
Kit.ZONE (the defaults) and Kit.ZONE_CHOICES.
token
Make gems bigger, glossier, more outlined
gem.gd: BODY, GLOSS, OUTLINE, MARK.
token
Change how loud wires and charge are
wires_view.gd: GRID, NODE, THICK, LIVE_THICK, JITTER, FLICKER, BLINK.
token
Change the frontier's look
frontier.gd: BAR, RAIL, PIECE, WASH.
token
Speed up or slow down animations
config.json · timing, and the effect lengths in board_view.gd.
token
A new shape for units, pads or towers
Kit.diamond, structures_view.gd · _pad_outline and SHAPE.
code
Bitmaps, gradients, textures, shadows
Nothing in the game draws one today; each layer would need it added.
code
A typeface
See Type above.
code
A different layout of the rail or the screen
battle.gd · _layout(), side_panel.gd.
code