Tiny World "Block" button style (locked design language)
The canonical look for tool/block buttons (the floating palette .tool-palette,
the group popout .flyout, and the bottom toolbar .tool blocks). Jason signed
off on this as the house style — reuse it for any new icon square/tile.
The three ingredients
- Outlined square — a
1.5px border in a darkened version of the tile's
category color, an inner white line, and a slight raised lift.
- Outlined glyph — a white icon body with a matching dark colored outline,
drawn with
paint-order: stroke + vector-effect: non-scaling-stroke so one
stroke-width looks identical across every glyph viewBox (24, 512, …).
- Category color — the dark outline is the darkened category tint; the
square keeps a faint translucent category background.
Category palette
| posType |
background tint |
dark outline (border + glyph stroke) |
| terrain |
rgba(123,194,48,0.16) |
#3a6511 |
| primary |
rgba(23,107,235,0.13) |
#1e428a |
| tertiary |
rgba(254,146,14,0.18) |
#a05600 |
| shield |
rgba(45,215,255,0.15) |
#0e5f7e |
| neutral |
(none) |
#2f3b57 (default --glyph-outline) |
The dark outline = the category tint darkened ~50–60%. For a new category,
pick the same hue at roughly that lightness.
Recipe (lives in styles/tiny-world.css)
Glyph (white body + colored outline):
.tool .tool-glyph { color: #fff; --glyph-outline: #2f3b57; }
.tool .tool-glyph svg { fill: currentColor; overflow: visible; }
.tool .tool-glyph svg * {
fill: currentColor;
stroke: var(--glyph-outline);
stroke-width: 2.1;
vector-effect: non-scaling-stroke;
paint-order: stroke;
stroke-linejoin: round;
stroke-linecap: round;
}
/* per posType: set --glyph-outline on the .tool-glyph */
Raised, outlined square (per posType; same pattern for .tool and
.tool.flyout-tool[data-pos-type]):
.tool[data-pos-type="primary"] {
border: 1.5px solid #1e428a; /* dark category outline */
box-shadow:
inset 0 0 0 1.5px rgba(255,255,255,0.92), /* inner white line */
inset 0 -3px 0 rgba(30,66,138,0.22), /* bottom bevel */
0 2px 3px -1px rgba(20,30,50,0.22); /* raised drop shadow */
}
Applying it to a new button/tile
- Give the tile a
data-pos-type (terrain/primary/tertiary) — the existing
rules then paint it automatically. posTypeForTool() in
19-tools-toolbar.js maps a tool to its posType.
- Bottom-toolbar buttons must all carry a stable
data-pos-type: Select uses
primary, Erase uses neutral, and inactive group buttons use
posTypeForToolGroup(group) so their borders do not disappear until a group
becomes active. The late Unified block buttons CSS block applies the same
raised border/inner-line treatment to .toolbar .tool,
.toolbar .tool-group-btn, .flyout .tool.flyout-tool, and the floating
.tool-palette; keep that block after generic flyout sizing rules.
- Selection radial buttons stay circular, but they still use the same category
contract.
33-radial-menu.js passes data-pos-type through makeBtn()
(root actions = primary, close/back = neutral, reset swatch = neutral).
Close/Back sits as the compact center button (.radial-center), with action
buttons on a tight orbit; do not put close/back back into an outer top slot or
expand the ring radius. The mooring style radial in 36-mooring-interaction.js
uses tertiary. Style them via .radial-btn[data-pos-type], not by returning
to pale generic circles.
- Radial labels/icons need a separate high-contrast
--radial-ink on an
opaque --radial-bg. Do not use very transparent category fills for radial
buttons; the 3D scene bleeds through and makes the icon text unreadable.
- Appbar icon buttons, the single language picker trigger, and the left side-rail
controls are also category chrome now. Keep
data-pos-type on those static
HTML buttons and style them through .appbar .btn.icon[data-pos-type],
.controls .btn.icon[data-pos-type], and .language-trigger[data-pos-type].
The language choices live in the upward menu; do not put the four-flag strip
back in the appbar.
- The top-center
.world-pill[data-pos-type="primary"] keeps the primary blue
outline, but its resting fill is deliberately bright white glass
(rgba(255,255,255,0.82)) rather than the generic blue primary tint so the
world selector matches the whiter chrome surfaces. Its hover state must keep
transform: translateX(-50%); do not apply the generic hover lift to this
fixed centered pill or it jumps.
- The token corner is deliberately plain text, not a pill panel. Keep
.token-corner free of data-pos-type, borders, backdrop blur, and hover
transforms; only the nested GitHub link uses
.token-corner .btn.icon[data-pos-type] block-button chrome.
- Build the icon with
buildToolButton(tool, { flyout: true }) so it reuses the
.tool-glyph / .tool-icon machinery and the outline rules.
- Bottom-toolbar utility buttons use
buildToolbarUtilityButton(...) and the
same .tool icon-only block square. Keep Home and Shield as adjacent utility
buttons at the front of the bottom toolbar; Shield uses data-pos-type="shield"
and reflects the raised/lowered VoxelShield state with .active and
aria-pressed.
- Active/pressed toolbar states must override dark/after-hours theme rules: keep
the category outline color, use a light raised fill, a visible 2px outer ring,
and a short bottom accent bar so selected tools do not read like pale disabled
buttons. Because the theme blocks use
body.ui-theme-dark ... !important,
add matching body.ui-theme-dark active selectors after the generic selected
block; a lower-specificity late rule is not enough.
- If a component hard-codes button
background, color, border, or local
category variables (for example launch/game-type, auth, account, or corner
chrome buttons), add a component-scoped body.ui-theme-dark ... rule after the
light rule. Do not rely on inherited --ink: background: var(--ink) plus
color: #fff becomes unreadable when dark mode flips --ink to light text.
- Light-mode selected option chips/tabs in the selection and Layers properties
panels are part of the same glass language. Keep their old translucent fills
(
rgba(60,130,247,0.15) for active chips, rgba(23,107,235,0.14) for active
Layers tabs) plus backdrop blur; do not replace them with solid white fills.
- Mono/utility icons (select, erase) use
.tool-icon and render as
fill:none; stroke:currentColor outlines (the rule is shared between
.toolbar, .flyout, and .tool-palette). Keep them line-art, not filled.
- Never go back to solid-filled (
fill:currentColor; stroke:none) glyphs for
block buttons — that is the old look this style replaced.
Gotchas
vector-effect/paint-order must be on the path/shape elements
(svg *), not the <svg> — they don't inherit.
- The global
[data-tooltip]{position:relative} rule can clobber position at
tied specificity; if a styled button mispositions, raise selector specificity
(e.g. button.foo).
- Verify in a real browser, not synthetic events: sample the rendered pixels
(a primary block reads dark border
30,66,138 → white line ~250 → tint).
1---2name: tinyworld-block-button-style3description: Use when styling any tool/block button, palette tile, icon chip, or new clickable square in Tiny World Builder. The locked-in "block" aesthetic — a raised square with a dark category-colored outline, an inner white line, and a white-bodied glyph outlined in the same dark color. Apply this whenever a new icon button/tile is added so the UI stays consistent.4---56# Tiny World "Block" button style (locked design language)78The canonical look for tool/block buttons (the floating palette `.tool-palette`,9the group popout `.flyout`, and the bottom toolbar `.tool` blocks). Jason signed10off on this as the house style — reuse it for any new icon square/tile.1112## The three ingredients13141. **Outlined square** — a `1.5px` border in a *darkened* version of the tile's15 category color, an inner white line, and a slight raised lift.162. **Outlined glyph** — a white icon body with a matching dark colored outline,17 drawn with `paint-order: stroke` + `vector-effect: non-scaling-stroke` so one18 `stroke-width` looks identical across every glyph viewBox (24, 512, …).193. **Category color** — the dark outline is the darkened category tint; the20 square keeps a faint translucent category background.2122## Category palette2324| posType | background tint | dark outline (border + glyph stroke) |25|-----------|----------------------------|--------------------------------------|26| terrain | `rgba(123,194,48,0.16)` | `#3a6511` |27| primary | `rgba(23,107,235,0.13)` | `#1e428a` |28| tertiary | `rgba(254,146,14,0.18)` | `#a05600` |29| shield | `rgba(45,215,255,0.15)` | `#0e5f7e` |30| neutral | (none) | `#2f3b57` (default `--glyph-outline`)|3132The dark outline = the category tint darkened ~50–60%. For a new category,33pick the same hue at roughly that lightness.3435## Recipe (lives in `styles/tiny-world.css`)3637Glyph (white body + colored outline):3839```css40.tool .tool-glyph { color: #fff; --glyph-outline: #2f3b57; }41.tool .tool-glyph svg { fill: currentColor; overflow: visible; }42.tool .tool-glyph svg * {43 fill: currentColor;44 stroke: var(--glyph-outline);45 stroke-width: 2.1;46 vector-effect: non-scaling-stroke;47 paint-order: stroke;48 stroke-linejoin: round;49 stroke-linecap: round;50}51/* per posType: set --glyph-outline on the .tool-glyph */52```5354Raised, outlined square (per posType; same pattern for `.tool` and55`.tool.flyout-tool[data-pos-type]`):5657```css58.tool[data-pos-type="primary"] {59 border: 1.5px solid #1e428a; /* dark category outline */60 box-shadow:61 inset 0 0 0 1.5px rgba(255,255,255,0.92), /* inner white line */62 inset 0 -3px 0 rgba(30,66,138,0.22), /* bottom bevel */63 0 2px 3px -1px rgba(20,30,50,0.22); /* raised drop shadow */64}65```6667## Applying it to a new button/tile6869- Give the tile a `data-pos-type` (terrain/primary/tertiary) — the existing70 rules then paint it automatically. `posTypeForTool()` in71 `19-tools-toolbar.js` maps a tool to its posType.72- Bottom-toolbar buttons must all carry a stable `data-pos-type`: Select uses73 `primary`, Erase uses `neutral`, and inactive group buttons use74 `posTypeForToolGroup(group)` so their borders do not disappear until a group75 becomes active. The late `Unified block buttons` CSS block applies the same76 raised border/inner-line treatment to `.toolbar .tool`,77 `.toolbar .tool-group-btn`, `.flyout .tool.flyout-tool`, and the floating78 `.tool-palette`; keep that block after generic flyout sizing rules.79- Selection radial buttons stay circular, but they still use the same category80 contract. `33-radial-menu.js` passes `data-pos-type` through `makeBtn()`81 (root actions = `primary`, close/back = `neutral`, reset swatch = `neutral`).82 Close/Back sits as the compact center button (`.radial-center`), with action83 buttons on a tight orbit; do not put close/back back into an outer top slot or84 expand the ring radius. The mooring style radial in `36-mooring-interaction.js`85 uses `tertiary`. Style them via `.radial-btn[data-pos-type]`, not by returning86 to pale generic circles.87- Radial labels/icons need a separate high-contrast `--radial-ink` on an88 opaque `--radial-bg`. Do not use very transparent category fills for radial89 buttons; the 3D scene bleeds through and makes the icon text unreadable.90- Appbar icon buttons, the single language picker trigger, and the left side-rail91 controls are also category chrome now. Keep `data-pos-type` on those static92 HTML buttons and style them through `.appbar .btn.icon[data-pos-type]`,93 `.controls .btn.icon[data-pos-type]`, and `.language-trigger[data-pos-type]`.94 The language choices live in the upward menu; do not put the four-flag strip95 back in the appbar.96- The top-center `.world-pill[data-pos-type="primary"]` keeps the primary blue97 outline, but its resting fill is deliberately bright white glass98 (`rgba(255,255,255,0.82)`) rather than the generic blue primary tint so the99 world selector matches the whiter chrome surfaces. Its hover state must keep100 `transform: translateX(-50%)`; do not apply the generic hover lift to this101 fixed centered pill or it jumps.102- The token corner is deliberately plain text, not a pill panel. Keep103 `.token-corner` free of `data-pos-type`, borders, backdrop blur, and hover104 transforms; only the nested GitHub link uses105 `.token-corner .btn.icon[data-pos-type]` block-button chrome.106- Build the icon with `buildToolButton(tool, { flyout: true })` so it reuses the107 `.tool-glyph` / `.tool-icon` machinery and the outline rules.108- Bottom-toolbar utility buttons use `buildToolbarUtilityButton(...)` and the109 same `.tool icon-only` block square. Keep Home and Shield as adjacent utility110 buttons at the front of the bottom toolbar; Shield uses `data-pos-type="shield"`111 and reflects the raised/lowered `VoxelShield` state with `.active` and112 `aria-pressed`.113- Active/pressed toolbar states must override dark/after-hours theme rules: keep114 the category outline color, use a light raised fill, a visible 2px outer ring,115 and a short bottom accent bar so selected tools do not read like pale disabled116 buttons. Because the theme blocks use `body.ui-theme-dark ... !important`,117 add matching `body.ui-theme-dark` active selectors after the generic selected118 block; a lower-specificity late rule is not enough.119- If a component hard-codes button `background`, `color`, `border`, or local120 category variables (for example launch/game-type, auth, account, or corner121 chrome buttons), add a component-scoped `body.ui-theme-dark ...` rule after the122 light rule. Do not rely on inherited `--ink`: `background: var(--ink)` plus123 `color: #fff` becomes unreadable when dark mode flips `--ink` to light text.124- Light-mode selected option chips/tabs in the selection and Layers properties125 panels are part of the same glass language. Keep their old translucent fills126 (`rgba(60,130,247,0.15)` for active chips, `rgba(23,107,235,0.14)` for active127 Layers tabs) plus backdrop blur; do not replace them with solid white fills.128- Mono/utility icons (select, erase) use `.tool-icon` and render as129 `fill:none; stroke:currentColor` outlines (the rule is shared between130 `.toolbar`, `.flyout`, and `.tool-palette`). Keep them line-art, not filled.131- Never go back to solid-filled (`fill:currentColor; stroke:none`) glyphs for132 block buttons — that is the *old* look this style replaced.133134## Gotchas135136- `vector-effect`/`paint-order` must be on the path/shape elements137 (`svg *`), not the `<svg>` — they don't inherit.138- The global `[data-tooltip]{position:relative}` rule can clobber `position` at139 tied specificity; if a styled button mispositions, raise selector specificity140 (e.g. `button.foo`).141- Verify in a real browser, not synthetic events: sample the rendered pixels142 (a primary block reads dark border `30,66,138` → white line `~250` → tint).