Niveus: a tactile, adaptive interface language

Bevelled controls with light falling across them, fields as wells, solid
windows, and glass kept for the chrome that floats over everything else.
Warm graphite in dark, soft paper in light, one brand color driving both.
Ships its own fonts (Instrument Sans, JetBrains Mono; OFL).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-03 17:47:03 +02:00
commit 23b8dd376d
51 changed files with 15664 additions and 0 deletions

260
niveus/tokens/color.css Normal file
View file

@ -0,0 +1,260 @@
/* ==========================================================================
Niveus — Color
--------------------------------------------------------------------------
Two levels of tokens live here:
1. Palette Raw, appearance-agnostic values. Never referenced by
components. Prefixed --nv-palette-*.
2. Semantic The roles components actually consume (background, surface,
text-primary, accent, danger, …). Each role resolves with
light-dark(), pulling its value from the matching appearance
file: themes/light.css (--nv-light-*) or themes/dark.css
(--nv-dark-*).
The appearance is chosen by `color-scheme`, which themes/*.css bind to the
[data-theme] attribute. A component therefore never knows — and must never
ask — which appearance is active.
========================================================================== */
/* ==========================================================================
Brand
--------------------------------------------------------------------------
The whole color system has one input. Set --nv-brand and the accent, its
hover and active states, the text that lands on it, the focus ring, the
selection highlight, the tint in the greys and the lean of the status colors
are all re-derived from it — in both appearances, with no second value to
keep in sync.
:root { --nv-brand: #d92e3f; }
Set it on any element with [data-nv-brand] to re-brand a subtree.
========================================================================== */
:root {
--nv-brand: #4f6cff;
/* How much of the brand bleeds into the greys. Enough that the neutrals
belong to the same family as the accent; not enough to read as colored.
0% gives strictly neutral greys. */
--nv-brand-tint: 4%;
/* How far the status colors lean towards the brand. Red still reads as
danger and green still reads as success — they just stop looking borrowed
from another system. 0% keeps them literal. */
--nv-brand-harmony: 12%;
/* Where "too mid-toned to carry a label" begins, and how far either side a
color is pushed to escape it. Raising the pivot sends more brands dark
(white labels); lowering it sends more of them light (dark labels). */
--nv-contrast-pivot: 0.7;
--nv-contrast-dark: 0.53;
--nv-contrast-light: 0.78;
}
:root {
/* --- Neutral ----------------------------------------------------------
Warm graphite to warm paper. A grey that leans very slightly towards
yellow reads as a material (metal, enamel, paper); a grey that leans blue
reads as a screen. The lean is small enough that the brand tint still
decides the family. */
--nv-palette-neutral-0: #ffffff;
--nv-palette-neutral-25: #fcfcfb;
--nv-palette-neutral-50: #f8f8f6;
--nv-palette-neutral-100: #f2f1ee;
--nv-palette-neutral-150: #eae9e5;
--nv-palette-neutral-200: #e1e0db;
--nv-palette-neutral-300: #cfcec8;
--nv-palette-neutral-400: #aeada7;
--nv-palette-neutral-500: #87867f;
--nv-palette-neutral-600: #63625d;
--nv-palette-neutral-700: #4a4945;
--nv-palette-neutral-800: #343337;
--nv-palette-neutral-850: #2a2a2d;
--nv-palette-neutral-900: #1d1d20;
--nv-palette-neutral-925: #18181a;
--nv-palette-neutral-950: #131315;
--nv-palette-neutral-975: #0e0e10;
--nv-palette-neutral-1000: #08080a;
/* --- Window controls ----------------------------------------------------
The three lights at the start of a titlebar. They are the one place the
system uses fixed colors instead of the brand: their meaning is learned
from position and color together, and re-coloring them would unteach it. */
--nv-palette-window-close: #ff5f57;
--nv-palette-window-minimize: #febc2e;
--nv-palette-window-zoom: #28c840;
/* --- Accent (indigo) --------------------------------------------------- */
--nv-palette-accent-50: #eef2ff;
--nv-palette-accent-100: #dfe6ff;
--nv-palette-accent-200: #c4d0ff;
--nv-palette-accent-300: #9fb3ff;
--nv-palette-accent-400: #7691ff;
--nv-palette-accent-500: #4f6cff;
--nv-palette-accent-600: #3f5cf0;
--nv-palette-accent-700: #3450d6;
--nv-palette-accent-800: #2a3fa8;
--nv-palette-accent-900: #22347f;
/* --- Success (green) --------------------------------------------------- */
--nv-palette-success-50: #e8f9ef;
--nv-palette-success-100: #ccf0dc;
--nv-palette-success-200: #9ee2bc;
--nv-palette-success-300: #62cd96;
--nv-palette-success-400: #33b478;
--nv-palette-success-500: #189a60;
--nv-palette-success-600: #0f7d4d;
--nv-palette-success-700: #0c6440;
--nv-palette-success-800: #0a5034;
--nv-palette-success-900: #08412b;
/* --- Warning (amber) --------------------------------------------------- */
--nv-palette-warning-50: #fff6e6;
--nv-palette-warning-100: #ffe9c2;
--nv-palette-warning-200: #ffd68d;
--nv-palette-warning-300: #fbbe52;
--nv-palette-warning-400: #eda520;
--nv-palette-warning-500: #cf8809;
--nv-palette-warning-600: #a96d05;
--nv-palette-warning-700: #855505;
--nv-palette-warning-800: #6a4406;
--nv-palette-warning-900: #563806;
/* --- Danger (red) ------------------------------------------------------ */
--nv-palette-danger-50: #ffeff1;
--nv-palette-danger-100: #ffdbde;
--nv-palette-danger-200: #ffb9bf;
--nv-palette-danger-300: #ff8b96;
--nv-palette-danger-400: #f95b6c;
--nv-palette-danger-500: #e2334a;
--nv-palette-danger-600: #c01f36;
--nv-palette-danger-700: #9c172b;
--nv-palette-danger-800: #7e1524;
--nv-palette-danger-900: #67141f;
}
/* ==========================================================================
Semantic roles
========================================================================== */
/* The subtree selector has to be repeated here, not just on the theme files:
var() inside a custom property is substituted where that property is
*declared*, so a --nv-color-* declared only on :root would bake in the root
brand and inherit the result unchanged. Re-declaring on [data-nv-brand]
re-runs the substitution against the subtree's own brand. */
:root,
[data-nv-brand] {
/* --- Canvas and surfaces ----------------------------------------------- */
--nv-color-background: light-dark(var(--nv-light-background), var(--nv-dark-background));
--nv-color-background-subtle: light-dark(var(--nv-light-background-subtle), var(--nv-dark-background-subtle));
--nv-color-surface: light-dark(var(--nv-light-surface), var(--nv-dark-surface));
--nv-color-surface-elevated: light-dark(var(--nv-light-surface-elevated), var(--nv-dark-surface-elevated));
--nv-color-surface-inset: light-dark(var(--nv-light-surface-inset), var(--nv-dark-surface-inset));
--nv-color-surface-hover: light-dark(var(--nv-light-surface-hover), var(--nv-dark-surface-hover));
--nv-color-surface-active: light-dark(var(--nv-light-surface-active), var(--nv-dark-surface-active));
--nv-color-scrim: light-dark(var(--nv-light-scrim), var(--nv-dark-scrim));
/* --- Text --------------------------------------------------------------- */
--nv-color-text-primary: light-dark(var(--nv-light-text-primary), var(--nv-dark-text-primary));
--nv-color-text-secondary: light-dark(var(--nv-light-text-secondary), var(--nv-dark-text-secondary));
--nv-color-text-tertiary: light-dark(var(--nv-light-text-tertiary), var(--nv-dark-text-tertiary));
--nv-color-text-disabled: light-dark(var(--nv-light-text-disabled), var(--nv-dark-text-disabled));
--nv-color-text-inverted: light-dark(var(--nv-light-text-inverted), var(--nv-dark-text-inverted));
/* --- Borders ------------------------------------------------------------ */
--nv-color-border-subtle: light-dark(var(--nv-light-border-subtle), var(--nv-dark-border-subtle));
--nv-color-border: light-dark(var(--nv-light-border), var(--nv-dark-border));
--nv-color-border-strong: light-dark(var(--nv-light-border-strong), var(--nv-dark-border-strong));
/* --- Accent ------------------------------------------------------------- */
--nv-color-accent: light-dark(var(--nv-light-accent), var(--nv-dark-accent));
--nv-color-accent-hover: light-dark(var(--nv-light-accent-hover), var(--nv-dark-accent-hover));
--nv-color-accent-active: light-dark(var(--nv-light-accent-active), var(--nv-dark-accent-active));
--nv-color-accent-subtle: light-dark(var(--nv-light-accent-subtle), var(--nv-dark-accent-subtle));
--nv-color-accent-subtle-hover: light-dark(var(--nv-light-accent-subtle-hover), var(--nv-dark-accent-subtle-hover));
--nv-color-accent-border: light-dark(var(--nv-light-accent-border), var(--nv-dark-accent-border));
--nv-color-accent-text: light-dark(var(--nv-light-accent-text), var(--nv-dark-accent-text));
--nv-color-on-accent: light-dark(var(--nv-light-on-accent), var(--nv-dark-on-accent));
/* --- Success ------------------------------------------------------------ */
--nv-color-success: light-dark(var(--nv-light-success), var(--nv-dark-success));
--nv-color-success-hover: light-dark(var(--nv-light-success-hover), var(--nv-dark-success-hover));
--nv-color-success-subtle: light-dark(var(--nv-light-success-subtle), var(--nv-dark-success-subtle));
--nv-color-success-border: light-dark(var(--nv-light-success-border), var(--nv-dark-success-border));
--nv-color-success-text: light-dark(var(--nv-light-success-text), var(--nv-dark-success-text));
--nv-color-on-success: light-dark(var(--nv-light-on-success), var(--nv-dark-on-success));
/* --- Warning ------------------------------------------------------------ */
--nv-color-warning: light-dark(var(--nv-light-warning), var(--nv-dark-warning));
--nv-color-warning-hover: light-dark(var(--nv-light-warning-hover), var(--nv-dark-warning-hover));
--nv-color-warning-subtle: light-dark(var(--nv-light-warning-subtle), var(--nv-dark-warning-subtle));
--nv-color-warning-border: light-dark(var(--nv-light-warning-border), var(--nv-dark-warning-border));
--nv-color-warning-text: light-dark(var(--nv-light-warning-text), var(--nv-dark-warning-text));
--nv-color-on-warning: light-dark(var(--nv-light-on-warning), var(--nv-dark-on-warning));
/* --- Danger ------------------------------------------------------------- */
--nv-color-danger: light-dark(var(--nv-light-danger), var(--nv-dark-danger));
--nv-color-danger-hover: light-dark(var(--nv-light-danger-hover), var(--nv-dark-danger-hover));
--nv-color-danger-active: light-dark(var(--nv-light-danger-active), var(--nv-dark-danger-active));
--nv-color-danger-subtle: light-dark(var(--nv-light-danger-subtle), var(--nv-dark-danger-subtle));
--nv-color-danger-border: light-dark(var(--nv-light-danger-border), var(--nv-dark-danger-border));
--nv-color-danger-text: light-dark(var(--nv-light-danger-text), var(--nv-dark-danger-text));
--nv-color-on-danger: light-dark(var(--nv-light-on-danger), var(--nv-dark-on-danger));
/* --- Depth and material ------------------------------------------------- */
--nv-color-shadow-ambient: light-dark(var(--nv-light-shadow-ambient), var(--nv-dark-shadow-ambient));
--nv-color-shadow-penumbra: light-dark(var(--nv-light-shadow-penumbra), var(--nv-dark-shadow-penumbra));
--nv-color-shadow-umbra: light-dark(var(--nv-light-shadow-umbra), var(--nv-dark-shadow-umbra));
--nv-color-shadow-inset: light-dark(var(--nv-light-shadow-inset), var(--nv-dark-shadow-inset));
--nv-color-highlight: light-dark(var(--nv-light-highlight), var(--nv-dark-highlight));
--nv-color-highlight-strong: light-dark(var(--nv-light-highlight-strong), var(--nv-dark-highlight-strong));
/* The bevel's other half: the shade along a raised surface's bottom edge,
and the contact line around anything that sits on another surface. Light
along the top and shade along the bottom is what makes a control read as
something pressable instead of a painted rectangle. */
--nv-color-lowlight: light-dark(var(--nv-light-lowlight), var(--nv-dark-lowlight));
--nv-color-outline: light-dark(var(--nv-light-outline), var(--nv-dark-outline));
/* Sheen: the light falling across a raised face, top to bottom. The plain
pair is for neutral controls and surfaces; the strong pair is for filled
color (primary buttons, checked marks), where it has to read through a
saturated fill. Both are laid over a surface's own color, never instead
of it. */
--nv-color-sheen-top: light-dark(var(--nv-light-sheen-top), var(--nv-dark-sheen-top));
--nv-color-sheen-bottom: light-dark(var(--nv-light-sheen-bottom), var(--nv-dark-sheen-bottom));
--nv-color-sheen-strong-top: light-dark(var(--nv-light-sheen-strong-top), var(--nv-dark-sheen-strong-top));
--nv-color-sheen-strong-bottom: light-dark(var(--nv-light-sheen-strong-bottom), var(--nv-dark-sheen-strong-bottom));
/* A knob: the thumb of a switch or a slider. It stays light in both
appearances, the way a physical knob does not change color with the room. */
--nv-color-knob: light-dark(var(--nv-light-knob), var(--nv-dark-knob));
--nv-color-knob-shade: light-dark(var(--nv-light-knob-shade), var(--nv-dark-knob-shade));
/* Elevation tint: on a dark canvas a shadow has nothing to darken, so a
raised surface also has to gain light. Transparent in light, where the
shadow does all the work. */
--nv-color-elevation-tint-1: light-dark(var(--nv-light-elevation-tint-1), var(--nv-dark-elevation-tint-1));
--nv-color-elevation-tint-2: light-dark(var(--nv-light-elevation-tint-2), var(--nv-dark-elevation-tint-2));
--nv-color-elevation-tint-3: light-dark(var(--nv-light-elevation-tint-3), var(--nv-dark-elevation-tint-3));
--nv-color-elevation-tint-4: light-dark(var(--nv-light-elevation-tint-4), var(--nv-dark-elevation-tint-4));
--nv-color-elevation-tint-5: light-dark(var(--nv-light-elevation-tint-5), var(--nv-dark-elevation-tint-5));
--nv-color-material-frosted: light-dark(var(--nv-light-material-frosted), var(--nv-dark-material-frosted));
--nv-color-material-glass: light-dark(var(--nv-light-material-glass), var(--nv-dark-material-glass));
--nv-color-material-border: light-dark(var(--nv-light-material-border), var(--nv-dark-material-border));
/* --- Window controls ------------------------------------------------------- */
--nv-color-window-close: light-dark(var(--nv-light-window-close), var(--nv-dark-window-close));
--nv-color-window-minimize: light-dark(var(--nv-light-window-minimize), var(--nv-dark-window-minimize));
--nv-color-window-zoom: light-dark(var(--nv-light-window-zoom), var(--nv-dark-window-zoom));
--nv-color-window-inactive: light-dark(var(--nv-light-window-inactive), var(--nv-dark-window-inactive));
--nv-color-window-control-ring: light-dark(var(--nv-light-window-control-ring), var(--nv-dark-window-control-ring));
--nv-color-window-glyph: light-dark(var(--nv-light-window-glyph), var(--nv-dark-window-glyph));
/* --- Interaction --------------------------------------------------------- */
--nv-color-focus-ring: light-dark(var(--nv-light-focus-ring), var(--nv-dark-focus-ring));
--nv-color-focus-ring-contrast: light-dark(var(--nv-light-focus-ring-contrast), var(--nv-dark-focus-ring-contrast));
--nv-color-selection: light-dark(var(--nv-light-selection), var(--nv-dark-selection));
}