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 #818CF8 with 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 as pulse below 28 px and under prefers-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.

Edit this page on GitLab

↑ ↓ to moveEnter to openEsc to close