|
| 1 | +# Design System |
| 2 | + |
| 3 | +This is the canonical visual style guide for the Meshpoint local |
| 4 | +dashboard (`frontend/`). Read it before opening any pull request that |
| 5 | +touches `frontend/`. Every PR that adds or changes UI code is |
| 6 | +reviewed against this document. |
| 7 | + |
| 8 | +Scope of this guide is the Meshpoint local dashboard only. The |
| 9 | +Meshradar cloud dashboard has its own (separate) design system. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Brand Identity |
| 14 | + |
| 15 | +UI strings, page titles, and any user-visible prose must follow the |
| 16 | +Meshpoint brand rules: |
| 17 | + |
| 18 | +- The device is **Meshpoint**: one word, capital M. Never |
| 19 | + "Mesh Point", "MeshPoint", or "mesh point". |
| 20 | +- The platform is **Meshradar**: one word, capital M. Never |
| 21 | + "Mesh Radar", "MeshRadar", or "mesh radar". |
| 22 | +- Plurals are **Meshpoints** and **Meshradars**. |
| 23 | +- Code identifiers (CSS class names, JS variables, config keys) keep |
| 24 | + existing project conventions (`top-bar`, `meshpoint`, `mesh_point`). |
| 25 | + The branding rule applies to prose, docs, UI strings, and |
| 26 | + user-facing text only. |
| 27 | + |
| 28 | +Writing style for UI copy: |
| 29 | + |
| 30 | +- Never use em dashes (`—`). Use a colon (`:`) or rewrite the sentence. |
| 31 | +- Never use en dashes (`–`) as punctuation. Use a colon or rewrite. |
| 32 | +- Hyphens (`-`) are fine for compound words. |
| 33 | + |
| 34 | +Page `<title>` rule: |
| 35 | + |
| 36 | +- The `<title>` should read `Meshpoint Dashboard` (or a similarly |
| 37 | + Meshpoint-branded short string). It must not introduce a new |
| 38 | + tagline or marketing line. |
| 39 | + |
| 40 | +> **Known bug, separate fix.** The current |
| 41 | +> `frontend/index.html` `<title>` reads `Mesh Radar - Mesh Point`, |
| 42 | +> which violates the brand rule. A polish PR will correct this. |
| 43 | +> Do not bundle that change with unrelated work. |
| 44 | +
|
| 45 | +--- |
| 46 | + |
| 47 | +## Color Tokens |
| 48 | + |
| 49 | +The canonical token palette lives in `frontend/css/dashboard.css` |
| 50 | +in the `:root` block. New CSS in `frontend/` MUST use these tokens |
| 51 | +and MUST NOT redeclare or rename them. |
| 52 | + |
| 53 | +### Backgrounds |
| 54 | + |
| 55 | +| Token | Value | Usage | |
| 56 | +|---|---|---| |
| 57 | +| `--bg-primary` | `#0a0e17` | Page background, deep base | |
| 58 | +| `--bg-secondary` | `#111827` | Top bar, table headers, packet detail rows | |
| 59 | +| `--bg-card` | `#162033` | Stat cards, drawer header, control surfaces | |
| 60 | +| `--bg-glass` | `rgba(22, 32, 51, 0.7)` | Panel surfaces (with `backdrop-filter: blur(12px)`) | |
| 61 | + |
| 62 | +### Borders |
| 63 | + |
| 64 | +| Token | Value | Usage | |
| 65 | +|---|---|---| |
| 66 | +| `--border` | `#233049` | All static borders | |
| 67 | +| `--border-glow` | `rgba(6, 182, 212, 0.2)` | Hover borders on `.panel` | |
| 68 | + |
| 69 | +### Text |
| 70 | + |
| 71 | +| Token | Value | Usage | |
| 72 | +|---|---|---| |
| 73 | +| `--text-primary` | `#e2e8f0` | Body copy, headings, strong values | |
| 74 | +| `--text-secondary` | `#94a3b8` | Secondary copy, table cells | |
| 75 | +| `--text-muted` | `#64748b` | Labels, hints, captions, meta | |
| 76 | + |
| 77 | +### Accents |
| 78 | + |
| 79 | +| Token | Value | Usage | |
| 80 | +|---|---|---| |
| 81 | +| `--accent-cyan` | `#06b6d4` | Primary accent, hover, focus, mono numerics | |
| 82 | +| `--accent-green` | `#00e5a0` | Success, online, active device, relay stats | |
| 83 | +| `--accent-blue` | `#3b82f6` | Meshtastic protocol indicator | |
| 84 | +| `--accent-purple` | `#a855f7` | Meshcore protocol indicator | |
| 85 | +| `--accent-amber` | `#f59e0b` | Warnings, position packets, update badge | |
| 86 | +| `--accent-red` | `#ef4444` | Errors, encrypted packets, disconnected status | |
| 87 | + |
| 88 | +### Effects and shape |
| 89 | + |
| 90 | +| Token | Value | Usage | |
| 91 | +|---|---|---| |
| 92 | +| `--glow-cyan` | `0 0 8px rgba(6, 182, 212, 0.3)` | Cyan glow halo | |
| 93 | +| `--radius` | `8px` | Default corner radius for cards, panels, inputs | |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## Semantic Color Mapping |
| 98 | + |
| 99 | +These mappings already exist across the dashboard. New code that |
| 100 | +expresses the same concepts MUST reuse them rather than invent new |
| 101 | +mappings. |
| 102 | + |
| 103 | +### Protocol colors |
| 104 | + |
| 105 | +| Protocol | Token | |
| 106 | +|---|---| |
| 107 | +| Meshtastic | `--accent-blue` | |
| 108 | +| Meshcore | `--accent-purple` | |
| 109 | + |
| 110 | +### Packet type colors |
| 111 | + |
| 112 | +Lifted from `frontend/css/dashboard.css` (`.packet-table td.type-*`). |
| 113 | + |
| 114 | +| Packet type | Token | |
| 115 | +|---|---| |
| 116 | +| `text` | `--accent-green` | |
| 117 | +| `position` | `--accent-amber` | |
| 118 | +| `telemetry` | `--accent-cyan` | |
| 119 | +| `nodeinfo` | `--accent-purple` | |
| 120 | +| `encrypted` | `--accent-red` | |
| 121 | +| `routing` | `--text-muted` | |
| 122 | +| `traceroute` | `--accent-blue` | |
| 123 | +| `neighborinfo` | `--accent-blue` | |
| 124 | + |
| 125 | +### RSSI buckets |
| 126 | + |
| 127 | +Used in the packet table cells. |
| 128 | + |
| 129 | +| Bucket | Token | |
| 130 | +|---|---| |
| 131 | +| `rssi-good` | `--accent-green` | |
| 132 | +| `rssi-mid` | `--accent-amber` | |
| 133 | +| `rssi-bad` | `--accent-red` | |
| 134 | + |
| 135 | +### Signal-quality buckets |
| 136 | + |
| 137 | +Used in node card chips and messaging signal indicators. These are |
| 138 | +currently hardcoded hex (legacy), but the SEMANTIC mapping is |
| 139 | +canonical and MUST be preserved on any new component that displays |
| 140 | +signal quality. |
| 141 | + |
| 142 | +| Bucket | Hex | Notes | |
| 143 | +|---|---|---| |
| 144 | +| `excellent` | `#00e676` | Brighter than `--accent-green`, kept for visual contrast in chip context | |
| 145 | +| `good` | `#4ecdc4` | Legacy teal, slightly different from `--accent-cyan` | |
| 146 | +| `fair` | `#ffd54f` | Legacy yellow | |
| 147 | +| `poor` | `#ff5252` | Legacy red | |
| 148 | + |
| 149 | +A future cleanup PR will migrate these to tokens. Do not introduce |
| 150 | +NEW hardcoded colors; reuse one of these four if you need a quality |
| 151 | +bucket on a new component. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Typography |
| 156 | + |
| 157 | +Two font families are loaded from Google Fonts in `frontend/index.html`. |
| 158 | +Do not introduce additional font families. |
| 159 | + |
| 160 | +| Token | Family | Usage | |
| 161 | +|---|---|---| |
| 162 | +| `--font-sans` | `Inter` (with system fallback) | All prose: body, headings, labels, buttons | |
| 163 | +| `--font-mono` | `JetBrains Mono` (with `Fira Code` fallback) | Numerics, IDs, hashes, packet data, signal values, table cells | |
| 164 | + |
| 165 | +### Type scale (existing canonical sizes) |
| 166 | + |
| 167 | +| Surface | Size | Weight | Casing | |
| 168 | +|---|---|---|---| |
| 169 | +| Top-bar `<h1>` | `1.05rem` | 600 | mixed | |
| 170 | +| Top-bar status | `0.8rem` mono | 600 (stats), 400 (label) | mixed | |
| 171 | +| Tab button | `0.85rem` | 500 | mixed | |
| 172 | +| Panel header | `0.7rem` | 600 | UPPERCASE, `0.05em` letter-spacing | |
| 173 | +| Stat card label | `0.6rem` | normal | UPPERCASE, `0.05em` letter-spacing | |
| 174 | +| Stat card value | `1.1rem` mono | 700 | mixed | |
| 175 | +| Packet table header | `0.7rem` mono | 600 | UPPERCASE, `0.04em` letter-spacing | |
| 176 | +| Packet table cell | `0.78rem` mono | 400 | mixed | |
| 177 | +| Node card name | `0.8rem` | 600 | mixed | |
| 178 | +| Node card chip | `0.6rem` mono | 600-700 | mixed (or UPPERCASE for quality) | |
| 179 | + |
| 180 | +When in doubt, match the size and weight of an existing surface that |
| 181 | +plays the same role. |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## Layout Primitives |
| 186 | + |
| 187 | +The dashboard uses one shell pattern and a small set of container |
| 188 | +primitives. New work composes these. A change to the shell (for |
| 189 | +example, replacing the tab bar with a sidebar) is a design RFC, not |
| 190 | +a regular PR. |
| 191 | + |
| 192 | +### Shell |
| 193 | + |
| 194 | +``` |
| 195 | +<header class="top-bar"> ... </header> |
| 196 | +<nav class="tab-bar"> ... </nav> |
| 197 | +<div id="tab-X" class="tab-content tab-content--active"> ... </div> |
| 198 | +<div id="tab-Y" class="tab-content"> ... </div> |
| 199 | +``` |
| 200 | + |
| 201 | +The top bar is sticky (`position: sticky; top: 0; z-index: 1000`). |
| 202 | +The tab bar is a single-row horizontal strip with one |
| 203 | +`tab-bar__btn--active` at a time. Each tab body is a |
| 204 | +`.tab-content` div, only the active one is shown. |
| 205 | + |
| 206 | +### `.panel` |
| 207 | + |
| 208 | +The single canonical container for any rectangular content surface. |
| 209 | + |
| 210 | +``` |
| 211 | +<div class="panel"> |
| 212 | + <div class="panel__header">Title</div> |
| 213 | + <div class="panel__body"> ... </div> |
| 214 | +</div> |
| 215 | +``` |
| 216 | + |
| 217 | +Backed by `--bg-glass` with `backdrop-filter: blur(12px)`, |
| 218 | +`--border`, and `--radius`. Hover state lifts the border to |
| 219 | +`--border-glow`. |
| 220 | + |
| 221 | +### `.stat-card` |
| 222 | + |
| 223 | +Small horizontal cards in a horizontally-scrollable strip |
| 224 | +(`.dashboard__stats`). Each card has a label, a large mono value, |
| 225 | +and an optional sub-line. Modifiers (`.stat-card--relay`, |
| 226 | +`.stat-card--system`) tint the border and value color to convey |
| 227 | +category. |
| 228 | + |
| 229 | +### Drawer (right-side slide-in) |
| 230 | + |
| 231 | +``` |
| 232 | +<div id="X-backdrop" class="nd-backdrop"></div> |
| 233 | +<div id="X-drawer" class="nd-drawer"> ... </div> |
| 234 | +``` |
| 235 | + |
| 236 | +Used for node detail today. Right-side, ~50% width, with a backdrop |
| 237 | +overlay. Reuse this primitive for any future detail-view that does |
| 238 | +not deserve its own tab. |
| 239 | + |
| 240 | +--- |
| 241 | + |
| 242 | +## Naming Convention |
| 243 | + |
| 244 | +All CSS class names follow **BEM**: |
| 245 | + |
| 246 | +- `block` for the component root: `top-bar`, `panel`, `stat-card` |
| 247 | +- `block__element` for a child of the block: `top-bar__brand`, |
| 248 | + `panel__header`, `stat-card__value` |
| 249 | +- `block--modifier` for a state or variant: `tab-bar__btn--active`, |
| 250 | + `stat-card--relay`, `nc-chip--excellent` |
| 251 | +- kebab-case throughout (no `camelCase` and no `snake_case`) |
| 252 | + |
| 253 | +Per-component prefixes already in use. New components MUST pick a |
| 254 | +prefix that does not collide with these and stick to BEM: |
| 255 | + |
| 256 | +| Prefix | Owner | |
| 257 | +|---|---| |
| 258 | +| `top-bar`, `tab-bar`, `tab-content` | shell | |
| 259 | +| `dashboard`, `panel`, `stat-card` | dashboard surface | |
| 260 | +| `packet-` | packet table | |
| 261 | +| `nc-` | node cards | |
| 262 | +| `nd-` | node drawer | |
| 263 | +| `msg-` | messaging | |
| 264 | +| `radio-` | radio settings | |
| 265 | +| `ss-` | stats summary | |
| 266 | +| `terminal-` | terminal (when it lands) | |
| 267 | + |
| 268 | +--- |
| 269 | + |
| 270 | +## File Organization |
| 271 | + |
| 272 | +- One CSS file per major UI surface, under `frontend/css/`. |
| 273 | + Today: `dashboard.css`, `node_cards.css`, `node_drawer.css`, |
| 274 | + `messaging.css`, `radio.css`, `stats.css`. |
| 275 | +- Every CSS file is loaded from `<head>` in `frontend/index.html` |
| 276 | + via a separate `<link rel="stylesheet">`. |
| 277 | +- Inline `<style>` blocks in `frontend/index.html` are not allowed |
| 278 | + for design-system rules. Per-page micro-tweaks under 10 lines are |
| 279 | + tolerated; anything larger goes in a CSS file. |
| 280 | +- Files MUST be saved as UTF-8 without BOM. CSS comments use plain |
| 281 | + ASCII separators (for example `/* ---- Section ---- */`), never |
| 282 | + Unicode box-drawing characters that may be miscoded. |
| 283 | + |
| 284 | +--- |
| 285 | + |
| 286 | +## Tech Debt: Legacy Token Drift |
| 287 | + |
| 288 | +Two existing files predate the canonical `:root` block and still use |
| 289 | +a legacy fallback namespace: |
| 290 | + |
| 291 | +- `frontend/css/messaging.css` and `frontend/css/radio.css` reference |
| 292 | + `var(--surface-0, #0f0f23)`, `var(--surface-1, #1a1a2e)`, |
| 293 | + `var(--surface-2, #22223a)`, `var(--accent, #4ecdc4)`, |
| 294 | + `var(--border, #2a2a4a)`. These fallback hex values are |
| 295 | + slightly different from the canonical tokens (the legacy teal |
| 296 | + `#4ecdc4` vs canonical cyan `#06b6d4`). |
| 297 | +- `frontend/css/node_cards.css` hardcodes a Material-style palette |
| 298 | + (`#00e676`, `#4ecdc4`, `#ffd54f`, `#ff5252`) for signal-quality |
| 299 | + chips. |
| 300 | + |
| 301 | +These files are grandfathered. **Do not extend the legacy |
| 302 | +namespace in new code.** All new CSS uses the canonical |
| 303 | +`--bg-*` / `--text-*` / `--accent-*` tokens documented above. A |
| 304 | +future cleanup PR will refactor the legacy files to the canonical |
| 305 | +tokens; that work is intentionally not bundled with regular UI |
| 306 | +changes. |
| 307 | + |
| 308 | +--- |
| 309 | + |
| 310 | +## Anti-Patterns (Do Not Do This) |
| 311 | + |
| 312 | +The following patterns will be flagged in PR review and the PR will |
| 313 | +be sent back for changes. PR #35 (April 29 2026) is the canonical |
| 314 | +example for several of these. |
| 315 | + |
| 316 | +1. **Do not redeclare `:root` design tokens with new names.** Adding |
| 317 | + `--bg-base`, `--amber`, `--teal`, `--text-hi`, etc. fragments the |
| 318 | + palette. Use the existing tokens. |
| 319 | +2. **Do not introduce new font families.** `Inter` and |
| 320 | + `JetBrains Mono` are the only families. Loading `Barlow`, |
| 321 | + `Barlow Condensed`, `Share Tech Mono`, etc. is rejected. |
| 322 | +3. **Do not put the design system in an inline `<style>` block in |
| 323 | + `index.html`.** The shell layout, color palette, and component |
| 324 | + styles live in `frontend/css/*.css` files. |
| 325 | +4. **Do not author CSS in non-UTF-8 encoding.** Save files as UTF-8 |
| 326 | + and view the diff in GitHub before pushing. Codepage round-trips |
| 327 | + (most often from terminal-based tools on Windows) can silently |
| 328 | + replace `─` with `ΓöÇ` and `·` with `┬╖`. |
| 329 | +5. **Do not change the page `<title>`, top-bar header text, or |
| 330 | + marketing copy** as part of a feature PR. Branding changes go |
| 331 | + in a separate, branding-only PR so they can be reviewed against |
| 332 | + `branding` rules in isolation. |
| 333 | +6. **Do not switch naming conventions inside a component.** A |
| 334 | + component is either BEM with double-underscore elements |
| 335 | + (`top-bar__brand`) or it is not. A mix of BEM and bare |
| 336 | + kebab-case (`panel-header`, `nav-item`, `top-stat-val`) inside |
| 337 | + one new component is rejected. |
| 338 | +7. **Do not replace the shell layout in a feature PR.** Changing |
| 339 | + `.top-bar` + `.tab-bar` + `.tab-content` to a sidebar shell or a |
| 340 | + different navigation primitive is a design RFC. Open an issue |
| 341 | + first; do not bundle a shell change with feature work. |
| 342 | +8. **Do not introduce a new color palette to "match a screenshot" |
| 343 | + from another product.** Meshpoint's identity is the cool |
| 344 | + cyan/green palette over deep navy. Warm amber/teal palettes, |
| 345 | + "hacker green" palettes, light-mode skins, etc. are out of scope. |
| 346 | + |
| 347 | +--- |
| 348 | + |
| 349 | +## Review Checklist (PR Author) |
| 350 | + |
| 351 | +Before opening a PR that touches `frontend/`, confirm: |
| 352 | + |
| 353 | +- No new `:root` token redeclarations. |
| 354 | +- No new `<link>` to a Google Fonts family beyond Inter and |
| 355 | + JetBrains Mono. |
| 356 | +- No inline `<style>` block in `index.html` larger than 10 lines. |
| 357 | +- New CSS classes follow BEM with an existing or new component |
| 358 | + prefix listed above. |
| 359 | +- All color values are CSS variables, not hardcoded hex. |
| 360 | + Exception: the four signal-quality buckets and the legacy |
| 361 | + `messaging.css` / `radio.css` / `node_cards.css` files are |
| 362 | + grandfathered. |
| 363 | +- Page `<title>`, top-bar header, and marketing strings unchanged |
| 364 | + unless this is explicitly a branding PR. |
| 365 | +- UI strings honor the brand rules above (`Meshpoint`, no em/en |
| 366 | + dashes). |
| 367 | +- New CSS files saved as UTF-8 without BOM, no mojibake in |
| 368 | + comments. |
| 369 | +- Shell layout (`.top-bar` + `.tab-bar` + `.tab-content`) |
| 370 | + unchanged unless an RFC issue was opened first. |
| 371 | + |
| 372 | +If you're not sure whether a change conforms, ask in the PR |
| 373 | +description rather than guessing. We would rather have the |
| 374 | +conversation early. |
| 375 | + |
| 376 | +--- |
| 377 | + |
| 378 | +## Related Documents |
| 379 | + |
| 380 | +- `.cursor/rules/branding.mdc` (private repo): brand voice and |
| 381 | + release-blurb style rules. UI strings here must obey the same |
| 382 | + rules. |
| 383 | +- `CONTRIBUTING.md`: general PR workflow, branch names, testing |
| 384 | + notes. |
0 commit comments