Source of truth for visual + interaction grammar across the labsly portfolio. Every labsly app implements its own DesignSystem that conforms to this guide. No shared Swift package in 2026 — this doc is the contract.
Published at https://labsly.com/style-guide.
See PLANS/06_LABSLY_SUBSTRATE.md and PLANS/07_LABSLY_BRAND.md for context.
Two Visual Tracks
Track A — labsly tools
Used by: Systema, Witness, Dais, Atlas, VoidRepublic, Meridian. Vibe: Mac-native restraint. Linear / Things / Fantastical references. Dark-mode-first. Lots of whitespace.
Track B — labsly craft
Used by: NovelWriter, future creative-craft apps. Vibe: Literary warmth. Scrivener / Ulysses / iA Writer references. Light-mode-first (with dark option). Paper textures. Reading-first.
Brand Mark — the Lissajous figure
The labsly mark is a Lissajous figure: the curve produced when two oscillations at different frequencies are plotted against each other — what an oscilloscope draws when you feed it two tones. The master figure is the 3:2 ratio.
It is a trace of a behaviour, not a drawing of an object, which is what keeps it from collapsing into a globe, an atom or a stack of layers. Two independent signals, plotted against each other, resolving into a stable pattern.
One figure, three cuts
| Cut | Where it goes |
|---|---|
| Screen | The glowing phosphor icon. App icon and large sizes — the bloom needs room. |
| Flat | The same figure with no glow and no graticule. Lockups and small UI. |
| Line | One colour, no container. Print, mono, inline. |
The glow never goes in a lockup. Beside a flat solid wordmark it reads as a different material, and at lockup size the bloom and graticule become noise. This is the same mismatch that made the previous mark sit badly against the wordmark.
The lockup uses the line cut, not the tile. A contained mark reads heavier
than the type beside it — the tile's edges add mass the wordmark has no answer
for. The bare figure sits with the letterforms instead of competing with them.
logo-tile*.svg keeps the contained version for places that want it.
Geometry (240-unit square)
| Element | Value |
|---|---|
| Figure | x = sin(3t + π/2), y = sin(2t), centred on 120,120 |
| Amplitude | rx 72, ry 68 (rx 76 / ry 72 for the line cut, which has no container) |
| Screen inset | 18 from the tile edge |
| Tile radius | 52 |
The screen inset is 18, not 28. The wider margin shrank the figure to roughly half the mark's height, so at lockup size the tile's outer box matched the line cuts while the figure inside them did not.
Denser ratios (5:4, 3:4, 7:4 …) are reserved for per-product variants — each product takes its own ratio and phosphor colour. The master mark is deliberately the simplest of the family. Ratios much denser than 3:2 turn to mush below 40px.
Assets
All files in public/brand/labsly/ are generated — never hand-edit them.
| File | Use |
|---|---|
icon.svg |
Phosphor screen. App icon; apple-touch-icon |
favicon.svg |
Simplified cut for 16–28px — no graticule, fatter trace. Mirrored to public/favicon.svg |
logo-dark.svg / logo.svg |
Primary lockup — line cut, for dark / light backgrounds |
logo-tile-dark.svg / logo-tile.svg |
Alternate lockup, for places that want the mark contained |
mark-dark.svg / mark.svg |
Flat tile alone, for dark / light backgrounds |
mono-ink.svg / mono-paper.svg / mono.svg |
Line cut, single colour (mono.svg uses currentColor) |
wordmark.svg / wordmark-dark.svg |
Wordmark alone (hand-authored, not generated) |
The -dark variants carry a hairline rim so the near-black tile separates from a
dark surface. public/brand/lb01/ holds LB-01, the previous mark, kept as a
mascot for docs, empty states and the build-in-public feed — not as identity.
Lockup alignment
| Rule | Value |
|---|---|
| Mark height | 0.96 × ascender |
| Vertical | centred on the cap block (ascender top → baseline) |
| Gap | 0.40 × ascender, from the true silhouette edge |
Measure the wordmark, do not read its viewBox. wordmark.svg declares
-4.6 -78.1 271.7 107.1, but its real ink is x 5.40..258.12, y -67.83..19.80
— roughly 9 units of padding on the bottom and right. Laying out against the
declared box desyncs the vertical centring and shifts the whole lockup left.
Four alignment rules were tried and rejected before this one, so do not re-derive them:
- bottom on the baseline — written for LB-01's tall portrait chassis; makes a square tile float high
- centre on the ink block — corrects placement, which was never the problem
- span the full ink block — sizes the mark at 1.29 × ascender and overpowers the wordmark
- any rule that leaves the mark overhanging the type at both ends — it then shares no edge with anything and reads adrift however precisely it is centred
Size was the defect, not placement. Verify changes by rendering the lockup and measuring the result, not by trusting the arithmetic.
To change the mark, edit scripts/build-brand.mjs and run:
npm run brand:build
Typography
Track A (tools)
- Font: SF Pro (system font, automatic on macOS)
- Display weight: Semibold (600)
- Body weight: Regular (400)
- Emphasis weight: Medium (500)
- Tracking: -0.5% on display, 0 on body
- Numerics: Tabular for data, proportional for prose
Track B (craft)
- Body font: Charter or New York (serif); Iowan Old Style fallback
- Display font: Same family; can use New York Display variant
- Sizes: 16–18pt body minimum (writers spend hours)
- Per-document overrides: Allow user to pick typeface, size, line-height
Type scale (both tracks)
| Token | Size (px) | Use |
|---|---|---|
| display-xl | 56–72 | Hero pages, splash |
| display-lg | 36–44 | Page titles |
| display | 28 | Section titles |
| heading | 20 | Subsection titles |
| body-lg | 17 | Lead paragraphs |
| body | 15 | Default body |
| caption | 13 | Labels, hints |
| micro | 11 | Tertiary info, badges |
Color Tokens
Track A (tools — dark-mode-first)
- Background: SF system materials (vibrancy + blur where appropriate); fallback
#0A0A0A - Foreground:
#F5F5F5 - Muted foreground:
#9A9A9A - Borders:
rgba(255,255,255,0.05)/rgba(255,255,255,0.1)for hover
Track A (tools — light-mode fallback)
- Background:
#FAFAFA - Foreground:
#1A1A1A - Muted foreground:
#6A6A6A
Track B (craft — light-mode-first)
- Background:
#FAF7F2(warm cream) - Foreground:
#2A2520(warm charcoal) - Muted:
#8A7F70 - Sage accent:
#7A8B6E - Ink red accent:
#9B2D2D
Track B (craft — dark-mode option)
- Background:
#1F1B16(warm dark) - Foreground:
#E8E4DD(warm cream-on-dark)
Per-app accent colors
| App | Accent |
|---|---|
| Systema | #5856D6 (indigo) |
| Atlas | #B87333 (bronze) |
| Service Design | #0FB5BA (teal) |
| Witness | #FF6B6B (coral) |
| Dais | #7B2DBE (royal purple) |
| NovelWriter | #9B2D2D (ink red) |
| VoidRepublic | #5B2D8B (cosmic violet) |
| Meridian | #2D8B5B (forest green) |
Semantic colors (both tracks, Apple-compatible)
- Positive:
#34C759(green) - Warning:
#FF9F0A(amber) - Critical:
#FF453A(red)
Spacing Scale
4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 (px). Stick to scale; no arbitrary values.
Radii
| Token | Value (px) | Use |
|---|---|---|
| sm | 4 | Inputs, small buttons |
| md | 8 | Cards, modals |
| lg | 12 | Large cards |
| xl | 16 | Panels |
| xxl | 20 | Hero blocks |
Shadows / Elevation
Three tiers per track. Use sparingly.
Track A (tools, dark)
- Subtle:
0 1px 0 rgba(255,255,255,0.05) inset - Card:
0 4px 16px rgba(0,0,0,0.3) - Modal:
0 16px 48px rgba(0,0,0,0.5)
Track B (craft, light)
- Subtle:
0 1px 2px rgba(0,0,0,0.04) - Card:
0 4px 12px rgba(0,0,0,0.06) - Modal:
0 16px 32px rgba(0,0,0,0.12)
Motion
Track A (tools)
- Easings: standard
ease-in-out, spring for natural interactions - Durations: 0.15–0.3s typical; 0.4s for navigation transitions
- No bouncing, no over-the-top entrance animations
Track B (craft)
- Easings: organic, longer (page-turn-like)
- Durations: 0.3–0.5s
- Effects: typewriter scrolling option
Required Patterns (cross-app, non-negotiable)
FadingScrollView (memory: feedback_scroll_hint.md)
Every scrollable modal MUST use a bottom fade hint to indicate more content. No exceptions.
Fullscreen toggle (memory: project_canvas_fullscreen.md)
Every canvas (CLD, stakeholder map, journey map, blueprint, Wardley map) MUST have a fullscreen toggle. No exceptions.
Persona output labeling (epistemic honesty)
Any AI-generated content surfacing personas, predictions, or analysis MUST display:
- Confidence indicator (visual or numeric)
- Source label (stated / inferred / synthesized / observed)
- Optional evidence/citations on hover or expand
This operationalizes the labsly anti-synthetic-persona positioning across the whole portfolio.
Voice & Tone
See PLANS/07_LABSLY_BRAND.md for full voice guidance.
Use: structured, rigor, leverage, system, signal, evidence, judgment, craft, epistemic, provenance, confidence Avoid: "supercharged," "revolutionary," "AI-powered," "smart," "magic," "seamless," "intuitive," "next-gen"
Last updated: 2026-08-27. Updates RFC-style: post it in public, gather feedback, ratify.