Brand
The Nistor mark, its lockups and the loader — the files in brand/, the rules for using them, and the loader in a page or in React.
The interface itself (accent, status colour, type scale, page anatomy, navigation) follows the design charter.
The brand kit lives in brand/ at the root of this repository: the mark, its colourways, the
lockups, the loader and the rules for using them. Every file there, and every copy of the mark in the product, is
generated from brand/mark.svg and brand/loader/states.json by node scripts/brand/build.mjs; CI runs
node scripts/brand/build.mjs --check and fails when a copy drifts.
The mark#
A01c "Six Cells": the upper triangle of the lineage matrix of three assets. The diagonal cells (the assets) are always
the accent #6366f1; the cells above it (the lineage edges) are the ink: #EAEBF2 on dark grounds, #0B0C12 on
light ones. The empty lower half is part of the mark.
Rules, in short (the full list is in brand/README.md):
- keep one cell (25 % of the mark's height) of clear space on every side; never smaller than 16 px (the horizontal lockup: 80 px wide);
- never rotate, recolour, reorder, outline or squash it, change its corner radius, fill the lower half or add effects; never put the accent cells on an accent ground (on indigo, use the all-white mark);
- one-colour use keeps all six cells.
UI colour system#
The interface's colours are the design tokens (@nistor-io/tokens, web/tokens/README.md),
following the design charter. The tables here and in
brand/README.md are generated from the token sources by
node scripts/brand/build.mjs, so they cannot drift.
#6366F1, the brand accent: the mark, illustrations, display type of 24 px and more. Never UI text or controls (4.47:1 on white).#4F46E5, the UI accent (--primary): the primary action, links, the current nav item, selection, focus. Dark theme: fill#818CF8with ink text, accent text#A5B4FC.
| Step | Indigo | Neutral |
|---|---|---|
| 0 | #FFFFFF |
|
| 25 | #F9FAFC |
|
| 50 | #EEF2FF |
#F4F5F9 |
| 100 | #E0E7FF |
#EEF0F6 |
| 150 | #E5E7EF |
|
| 200 | #C7D2FE |
#DCDFE6 |
| 300 | #A5B4FC |
#B9BDC8 |
| 400 | #818CF8 |
#8D92A0 |
| 500 | #6366F1 |
#7A7F8C |
| 600 | #4F46E5 |
#5F6473 |
| 700 | #4338CA |
#4A4F5E |
| 800 | #3730A3 |
#2A2E3A |
| 850 | #1B1E28 |
|
| 900 | #312E81 |
#14161E |
| 950 | #1E1B4B |
#0B0C12 |
| Role | Light | Dark | Use |
|---|---|---|---|
--page |
#F4F5F9 |
#0B0C12 |
the page ground behind the content |
--surface |
#FFFFFF |
#14161E |
content: tables, panels, cards, dialogs |
--surface-dim |
#F9FAFC |
#14161E |
a quieter area inside content (a table header, a code block) |
--surface-container |
#EEF0F6 |
#1B1E28 |
a filled control or a selected row on a surface |
--ink |
#0B0C12 |
#EAEBF2 |
body text and titles |
--ink-variant |
#4A4F5E |
#A9AEBD |
secondary text: labels, helper text, metadata |
--ink-muted |
#5F6473 |
#8D92A0 |
the quietest text: placeholders, disabled labels, timestamps |
--outline |
#7A7F8C |
#8D92A0 |
the border of a control (field, checkbox, outlined button) |
--outline-variant |
#DCDFE6 |
#2A2E3A |
hairlines: dividers, card and table borders |
--primary |
#4F46E5 |
#818CF8 |
the filled primary action (one per view) |
--primary-hover |
#4338CA |
#A5B4FC |
the primary action under the pointer |
--primary-pressed |
#3730A3 |
#C7D2FE |
the primary action while pressed |
--on-primary |
#FFFFFF |
#0B0C12 |
text and icons on the primary fill |
--primary-text |
#4F46E5 |
#A5B4FC |
links, the current nav item, selected tabs |
--primary-container |
#E0E7FF |
#282B45 |
the current nav item pill, selection, tonal buttons |
--on-primary-container |
#312E81 |
#E0E7FF |
text and icons on the primary container |
--inverse-surface |
#1B1E28 |
#EEF0F6 |
tooltips and the snackbar |
--inverse-ink |
#EAEBF2 |
#0B0C12 |
text on the inverse surface |
--inverse-primary |
#A5B4FC |
#4F46E5 |
the snackbar's action |
--scrim |
rgba(11, 12, 18, 0.4) |
rgba(0, 0, 0, 0.6) |
behind a dialog |
--state-hover |
rgba(11, 12, 18, 0.06) |
rgba(234, 235, 242, 0.08) |
the hover layer over any control or row |
--state-pressed |
rgba(11, 12, 18, 0.1) |
rgba(234, 235, 242, 0.12) |
the pressed layer over any control or row |
--focus-ring |
#4F46E5 |
#818CF8 |
the 2 px keyboard focus ring |
Text on each surface, light theme (minimum 4.5:1):
| Text role | on page | on surface | on surface dim | on container |
|---|---|---|---|---|
--ink |
17.92:1 | 19.52:1 | 18.69:1 | 17.13:1 |
--ink-variant |
7.49:1 | 8.16:1 | 7.82:1 | 7.16:1 |
--ink-muted |
5.41:1 | 5.90:1 | 5.65:1 | 5.18:1 |
--primary-text |
5.77:1 | 6.28:1 | 6.02:1 | 5.51:1 |
--error |
5.32:1 | 5.79:1 | 5.55:1 | 5.08:1 |
--warning |
4.98:1 | 5.42:1 | 5.19:1 | 4.76:1 |
--success |
4.70:1 | 5.13:1 | 4.91:1 | 4.50:1 |
--info |
4.72:1 | 5.14:1 | 4.92:1 | 4.51:1 |
--neutral |
5.41:1 | 5.90:1 | 5.65:1 | 5.18:1 |
Text on each surface, dark theme (minimum 4.5:1):
| Text role | on page | on surface | on surface dim | on container |
|---|---|---|---|---|
--ink |
16.42:1 | 15.18:1 | 15.18:1 | 13.98:1 |
--ink-variant |
8.81:1 | 8.14:1 | 8.14:1 | 7.50:1 |
--ink-muted |
6.27:1 | 5.80:1 | 5.80:1 | 5.34:1 |
--primary-text |
9.79:1 | 9.05:1 | 9.05:1 | 8.34:1 |
--error |
8.17:1 | 7.55:1 | 7.55:1 | 6.96:1 |
--warning |
11.97:1 | 11.07:1 | 11.07:1 | 10.20:1 |
--success |
9.96:1 | 9.21:1 | 9.21:1 | 8.48:1 |
--info |
9.26:1 | 8.56:1 | 8.56:1 | 7.88:1 |
--neutral |
6.27:1 | 5.80:1 | 5.80:1 | 5.34:1 |
Text on its fill (minimum 4.5:1):
| Text | On | Light | Dark |
|---|---|---|---|
--on-primary |
--primary |
6.28:1 | 6.54:1 |
--on-primary |
--primary-hover |
7.90:1 | 9.79:1 |
--on-primary |
--primary-pressed |
9.93:1 | 13.08:1 |
--on-primary-container |
--primary-container |
9.27:1 | 11.20:1 |
--on-error-container |
--error-container |
4.92:1 | 6.66:1 |
--on-warning-container |
--warning-container |
4.93:1 | 8.96:1 |
--on-success-container |
--success-container |
5.68:1 | 7.30:1 |
--on-info-container |
--info-container |
5.54:1 | 6.83:1 |
--on-neutral-container |
--neutral-container |
7.16:1 | 7.50:1 |
--inverse-ink |
--inverse-surface |
13.98:1 | 17.13:1 |
--inverse-primary |
--inverse-surface |
8.34:1 | 5.51:1 |
Borders and the focus ring on each surface (minimum 3:1, light / dark):
| Role | on page | on surface | on surface dim | on container |
|---|---|---|---|---|
--outline |
3.67:1 / 6.27:1 | 4.00:1 / 5.80:1 | 3.83:1 / 5.80:1 | 3.51:1 / 5.34:1 |
--focus-ring |
5.77:1 / 6.54:1 | 6.28:1 / 6.05:1 | 6.02:1 / 6.05:1 | 5.51:1 / 5.57:1 |
Status colours#
One set (error, warning, success, info, neutral) with containers, for every surface. Status colour is for status only: an icon plus its text, a badge, a banner, a chart series that means a state; never a fill, a border, a rail or a tint.
| Status | Theme | Icon and text | Container | Text on container | Contrast on surface | Contrast on container |
|---|---|---|---|---|---|---|
| error | light | #C5221F |
#FCE8E6 |
#C5221F |
5.79:1 | 4.92:1 |
| dark | #F28B82 |
#3B1715 |
#F28B82 |
7.55:1 | 6.66:1 | |
| warning | light | #9A5B00 |
#FEF3E0 |
#9A5B00 |
5.42:1 | 4.93:1 |
| dark | #F2C46B |
#33270E |
#F2C46B |
11.07:1 | 8.96:1 | |
| success | light | #1C7D4E |
#E3F3EA |
#146C2E |
5.13:1 | 5.68:1 |
| dark | #81C995 |
#12301F |
#81C995 |
9.21:1 | 7.30:1 | |
| info | light | #1B6CCE |
#E3EEFB |
#185ABC |
5.14:1 | 5.54:1 |
| dark | #8AB4F8 |
#102A4C |
#8AB4F8 |
8.56:1 | 6.83:1 | |
| neutral | light | #5F6473 |
#EEF0F6 |
#4A4F5E |
5.90:1 | 7.16:1 |
| dark | #8D92A0 |
#1B1E28 |
#A9AEBD |
5.80:1 | 7.50:1 |
Type#
Inter for everything, JetBrains Mono only for identifiers and code. The product scale is the charter's; the display scale is the website's alone.
Product scale (the shell, plugins, Nistor Cloud, the extensions, the docs):
| Role | Size | Line | Weight | Font | Use |
|---|---|---|---|---|---|
--type-title |
22px | 28px | 500 | Inter | the page title (one per page) |
--type-section |
16px | 24px | 600 | Inter | a section or card title |
--type-label |
14px | 20px | 600 | Inter | labels, buttons, tab and nav items |
--type-body |
14px | 20px | 400 | Inter | body text, fields, menus |
--type-table |
13px | 18px | 400 | Inter | cells of dense tables (compact density only) |
--type-caption |
12px | 16px | 400 | Inter | captions, helper text, badges: the floor |
--type-mono |
12.5px | 18px | 400 | JetBrains Mono | identifiers and code, nothing else |
Display scale (the website only):
| Role | Size | Line | Weight | Font | Use |
|---|---|---|---|---|---|
--type-display-1 |
32px | 40px | 600 | Inter | website section titles |
--type-display-2 |
44px | 52px | 600 | Inter | website page titles below the hero |
--type-hero |
clamp(2.25rem, 4vw + 1rem, 3.5rem) |
1.1 | 600 | Inter | the website hero (scales with the viewport) |
Shape and depth#
Radii 4 / 8 / 12 / pill; elevation 0 (flat cards) to 2 (dialogs, the snackbar); no glow, gradient or blur on any brand element, the social cards and store images included.
| Radius | Value | Use |
|---|---|---|
--radius-4 |
4px |
badges, chips, checkboxes, small controls |
--radius-8 |
8px |
fields, menus, tooltips, table containers |
--radius-12 |
12px |
cards, panels, dialogs |
--radius-pill |
999px |
buttons (design charter), the current nav item, filter chips |
| Elevation | Light | Dark | Use |
|---|---|---|---|
--elevation-0 |
none |
none |
everything on the page: cards and panels are flat, separated by hairlines |
--elevation-1 |
0 1px 2px rgba(11, 12, 18, 0.10), 0 4px 12px rgba(11, 12, 18, 0.10) |
0 1px 2px rgba(0, 0, 0, 0.40), 0 4px 12px rgba(0, 0, 0, 0.32) |
menus, tooltips, popovers |
--elevation-2 |
0 4px 8px rgba(11, 12, 18, 0.10), 0 12px 32px rgba(11, 12, 18, 0.18) |
0 4px 8px rgba(0, 0, 0, 0.40), 0 12px 32px rgba(0, 0, 0, 0.56) |
dialogs and the snackbar |
Icons#
lucide only, one pinned version across the shell, its plugins and the extensions (0.577.0); 16, 20 and 24 px, stroke
1.75 at 16 px and 2 above; no emoji anywhere in a UI; codicons only inside VS Code. Details:
brand/README.md.
Illustration#
Empty-state spot illustrations on the Six Cells grid (64 units, cells 16, gaps 4, corners 4), in two colours (the ink
of the ground and #6366F1), filled cells only, no outlines and no faces. Six to start: no data, no results, all
clear, error, not connected, locked. Details: brand/README.md.
Screenshots#
The real product only, captured from the demo with web/shell/scripts/capture-screens.mjs --scale 2, light theme,
1440 × 900 at 2×; cropped, never retouched. Details: brand/README.md.
Names#
Nistor, Nistor Cloud, Nistor Enterprise, Nistor Cloud admin (the staff console), Nistor Inspector (Chrome), Nistor
(VS Code); the AI entry point is "Ask AI", its page the "AI assistant", the proposals feature "Advisor".
nistor.cloud is a domain, never a name. The full table: brand/README.md.
Partners#
Partner logos unaltered, from the vendor's own asset page: full colour on light, the vendor's dark variant on dark,
or the colour logo on a white 8 px-radius chip when there is none; never recoloured with filters. Details:
brand/README.md.
The 16px exception#
The 16 px favicon is the only file with the mark under 16 px: the hinted 14 px layout of geometry.json at offset 1
inside the 16 px tile (decided 18 October 2026). Every icon size:
| Spec | Canvas | Ground | Tile | Mark | Layout | Files |
|---|---|---|---|---|---|---|
ico16 |
16 px | dark tile, two-colour mark | 16 px, radius 3 px | 14 px at 1 | hinted | favicon.ico (first image), Chrome icon-16.png |
ico32 |
32 px | dark tile, two-colour mark | 32 px, radius 5 px | 28 px at 2 | hinted | favicon.ico, Chrome icon-32.png |
ico48 |
48 px | dark tile, two-colour mark | 48 px, radius 7 px | 42 px at 3 | hinted | favicon.ico, Chrome icon-48.png |
apple |
180 px | dark tile, two-colour mark | 180 px, square (full bleed) | 112 px at 34 | master scaled | apple-touch-icon.png |
any192 |
192 px | dark tile, two-colour mark | 192 px, radius 22 % | 128 px at 32 | master scaled | icon-192.png (web manifests) |
any512 |
512 px | dark tile, two-colour mark | 512 px, radius 22 % | 336 px at 88 | master scaled | icon-512.png (web manifests) |
maskable512 |
512 px | dark tile, two-colour mark | 512 px, square (full bleed) | 288 px at 112 | master scaled | icon-maskable-512.png (web manifests, purpose maskable) |
chrome128 |
128 px | dark tile, two-colour mark | 96 px at 16, radius 15 px | 80 px at 24 | master scaled | Chrome icon-128.png (the store asks for 96 px artwork in a 128 px canvas) |
vscode256 |
256 px | dark tile, two-colour mark | 256 px, radius 22 % | 160 px at 48 | master scaled | VS Code media/icon.png |
workspace32 |
32 px | indigo tile, white mark | 32 px, square (full bleed) | 22 px at 5 | hinted | brand/workspace-marketplace/icon-32.png |
workspace48 |
48 px | indigo tile, white mark | 48 px, square (full bleed) | 32 px at 8 | master scaled | brand/workspace-marketplace/icon-48.png |
workspace96 |
96 px | indigo tile, white mark | 96 px, square (full bleed) | 64 px at 16 | master scaled | brand/workspace-marketplace/icon-96.png |
workspace128 |
128 px | indigo tile, white mark | 128 px, square (full bleed) | 80 px at 24 | master scaled | brand/workspace-marketplace/icon-128.png |
Downloads#
| Need | File in brand/ |
|---|---|
| the mark on a dark / light ground | mark-on-dark.svg, mark-on-light.svg |
| one colour / white | mark-one-colour.svg, mark-white.svg |
| an app tile (README, avatars) | tile-indigo.svg, tile-dark.svg |
| mark + name | lockup-horizontal-{dark,light,one-colour,white}.svg (default), lockup-stacked-*.svg |
an animated logo where only <img> works |
loader/loader-loop-{dark,light}.svg, loader/loader-intro-{dark,light}.svg |
| the loader's states as stills | states/state-{mark,zeros,flat,n,line,bracket}.svg |
| the secondary monogram | matrix-n.svg |
| the Google OAuth consent screen's logo | consent/consent-logo-120.png |
| the Google Workspace Marketplace icons | workspace-marketplace/icon-{32,48,96,128}.png |
The website publishes the same files under /brand/, with PNG renders at 1× and 2× and the social images (avatars,
banners, the repository social preview, store images). The Chrome Web Store promo tile and marquee are also in
chrome-extension/store/.
In a README or a doc, use the SVG files with <img>:
<img src="brand/lockup-horizontal-light.svg" height="48" alt="Nistor">
<img src="brand/loader/loader-loop-light.svg" width="48" height="48" alt="Loading">
The name is "Nistor" in titles and prose; the wordmark in the lockups is the lowercase nistor.
The loader#
The loader morphs the mark through five related states (zeros, flat, Matrix N, line, bracket) and always lands back on the mark. Modes:
loop(default): the full cycle, 7.1 s; renders aspulsebelow 28 px and underprefers-reduced-motion;intro: the mark assembles once (0.9 s) and stays; under reduced motion it shows the still mark;pulse: the mark stands still and the diagonal dims in turn (inline spinners);static: one state, still (state="n"…).
Page and route loaders appear only after 400 ms of loading and, once shown, stay at least 600 ms; a splash fades out
over 350 ms after finish(). While loading the loader has role="status" and the label "Loading" (or the page name);
when done, role="img" and the label "Nistor". The ink follows currentColor.
Web component#
brand/loader/nistor-loader.js defines <nistor-loader> with no dependency (the shell serves a copy as
/nistor-loader.js for its boot splash):
<script src="nistor-loader.js"></script>
<nistor-loader style="width:64px;height:64px"></nistor-loader>
<nistor-loader mode="intro"></nistor-loader>
<nistor-loader mode="static" state="n"></nistor-loader>
<script>
// let the current move end, land on the mark, then go on
document.querySelector('nistor-loader').finish().then(() => { /* ready */ });
</script>
Events: nistor-loader-state (detail.state) when a state is reached, nistor-loader-done when it lands on the mark.
NistorLoader.stateAt(ms) gives the exact frame at any time, for tests and frame exports.
React (@nistor-io/ui)#
import { useEffect, useRef } from 'react';
import { NistorLoader, NistorMark } from '@nistor-io/ui';
function Splash({ ready, onFinished }) {
const ref = useRef(null);
useEffect(() => { if (ready) ref.current.finish().then(onFinished); }, [ready]);
return <NistorLoader ref={ref} size={64} label="Loading Nistor" />;
}
<NistorLoader mode="pulse" size={20} /> // inline spinner
<NistorLoader mode="static" state="n" /> // one state
<NistorMark size={24} /> // the mark itself, hinted at small sizes
NistorLoader props: mode (loop, intro, pulse, static), state (static only), size in px, label,
onDone, speed; its ref exposes finish(). NistorMark props: size, tone (two-colour or one-colour),
title (makes it an image with that label).
Changing the brand#
Edit only the inputs (brand/mark.svg, brand/loader/states.json with brand/loader/nistor-loader.js, or
brand/wordmark.svg through scripts/brand/outline_wordmark.py), then run node scripts/brand/build.mjs and commit
every changed file. New or changed PNG/ICO files need a sign-off in scripts/ci/oss_reviewed.json before the leak gate
passes. For the website, run make brand NISTOR=<this checkout> in the website repository; for Nistor Cloud, run
node scripts/brand/build.mjs --enterprise <Nistor Enterprise checkout> here and commit what it writes there. The
repositories' GitLab avatars come from node scripts/brand/build.mjs --avatars <website checkout>/docs/brand/repo-logos
(one per repository listed in that folder's avatars.json, by class); setting them on GitLab is done by hand. A
change to the design tokens changes the generated tables of brand/README.md and this page: run the brand build after
the token build.
Trademarks#
The Apache License 2.0 of this repository grants no rights to the name "Nistor" or to the logos (section 6); see
brand/README.md.