jixoai-ui
the terminal design language
loading the style sheet…
Skip to content

Tokens

One variable is the whole identity

Colors are OKLCH with fixed lightness and chroma shared across every jixoai site; only --brand-hue varies. On this site it runs free — seeded from the time of day and one full day = one full 360° turn (4 minutes per degree). Use the palette popover in the header (or the slider below) to take manual control.

--brand-hue: 0° live OKLCH · fixed L/C law dark drift −4° radius 0 + bevel upgrade

hue runtime

The runtime seeds --brand-hue from the time of day (one full day = one full 360° turn) and the hue always equals the wall-clock position (4min/deg). The slider writes manually — pausing the cycle; the play/pause toggle resumes from wherever the hue is. Every swatch and the CSS law below bind to the LIVE hue.

--primary (current theme)

jixoai-ui

header brand eyebrow

press physics follow the hue automatically — no second variable
lib/jixoai.css
:root {
  --brand-hue: 0; /* runs free: 24h wall-clock, 4min/deg */

  --primary: oklch(0.6489 0.237 var(--brand-hue));
  --ring: var(--primary);
  --chart-1: var(--primary);
}
.dark {
  /* perceptual compensation: the hue drifts -4° toward dark */
  --primary: oklch(0.7044 0.1872 calc(var(--brand-hue) - 4));
}
hue-usage.svelte svelte
<script lang="ts">
  import { currentHue, playing, setHueManually, toggleHuePlay } from '$lib/hue-runtime.svelte';
</script>

<!-- one variable is the whole identity: the slider writes (auto-pauses),
     the toggle resumes toward the wall-clock hue (2s cubic-out) -->
let hue = $state(0);
let isPlaying = $state(true);
currentHue.subscribe((v) => (hue = v));
playing.subscribe((v) => (isPlaying = v));

<label for="hue-slider">--brand-hue</label>
<input
  id="hue-slider"
  type="range" min="0" max="359" step="1"
  value={Math.round(hue)}
  oninput={(e) => setHueManually(e.currentTarget.valueAsNumber)}
/>
<button type="button" onclick={toggleHuePlay}>
  {isPlaying ? '❚❚ pause' : '▶ play'}
</button>

Design Scale

The density kernel

One ruler, five densities (2xs: the opt-in pro-tool rung). Every dimension is an equation from --jx-unit (4px) and --jx-text-base (13px) — no hand-picked values. Components consume the inherited --jx-* tokens; [data-density] scopes switch all values simultaneously. Context injection (Kotlin Compose-inspired): providers set the scope, every child inherits.

Five densities, live

The same component at every density — text, spacing, hit targets, and media all scale from the ruler. No per-size branches in component CSS.

2xs
The density row text · line · media scale from the ruler
xs
The density row text · line · media scale from the ruler
sm
The density row text · line · media scale from the ruler
default
The density row text · line · media scale from the ruler
lg
The density row text · line · media scale from the ruler

The five-row table

All dimensions computed at the 16px root, columns in ladder order 2xs / xs / sm / default / lg. These are the RESOLVED values the kernel gate asserts in real Chromium. Two honest footnotes at 2xs: its hit is the ONE scoped floor (6U = 24px, the WCAG 2.5.8 AA minimum, inside [data-density='2xs'] only), and its secondary text degenerates to the primary size (the 0.625rem floor binds).

TokenDefaultSource
--jx-text10 / 11 / 12 / 13 / 15pxdensity
--jx-line14 / 16 / 18 / 20 / 24pxdensity
--jx-gap8 / 8 / 8 / 12 / 16pxdensity
--jx-stack4 / 4 / 4 / 8 / 8pxdensity
--jx-inset8 / 8 / 8 / 12 / 16pxdensity
--jx-row-min24 / 28 / 32 / 40 / 48pxdensity
--jx-hit24 / 28 / 32 / 40 / 48pxdensity
--jx-icon14 / 16 / 18 / 20 / 24pxdensity
--jx-image28 / 32 / 36 / 40 / 48pxdensity
--jx-text-secondary10 / 10 / 11 / 12 / 14pxdensity
--jx-unit0.25rem (4px)structural
--jx-text-base0.8125rem (13px)structural

Component supplement tokens

Components ADD tokens the global set doesn't cover — toggle geometry, slider rails, textarea heights. These are owned by their component and documented on each component's page.

TokenDefaultSource
--jx-toggle-trackvar(--jx-line)component
--jx-toggle-widthcalc(var(--jx-toggle-track) * 2)component
--jx-toggle-knobcalc(var(--jx-toggle-track) - var(--jx-unit))component
--jx-toggle-knob-border1pxcomponent
--jx-toggle-knob-border-colorvar(--primary)component
--jx-slider-trackmax(var(--jx-unit), calc(var(--jx-line) / 2))component
--jx-textarea-minmax(var(--jx-hit), calc(var(--jx-line) * 3 + ...))component
--jx-color-lanemax(var(--jx-hit), calc(var(--jx-icon) + ...))component

Usage

density usage
<!-- Scope switch: one attribute, every child inherits -->
<div data-density="sm">
  <MyComponent />  <!-- resolves to sm values -->
</div>

<!-- Svelte context: the provider pattern -->
<ItemGroup density="sm">
  <Item><!-- inherits sm from the group --></Item>
</ItemGroup>

<!-- Component supplement: override what the global doesn't cover -->
<style>
  .my-widget {
    min-height: var(--jx-hit);
    padding: var(--jx-stack) var(--jx-inset);
    font-size: var(--jx-text);
  }
</style>

Palette

The full sheet, current theme

Values are literal from the registry token sheet (light / dark where they differ). Switch the site theme (header toggle or palette popover) to compare the other mode — no dual panels here.

Neutrals (achromatic by law)

--background
oklch(1 0 0) / oklch(0 0 0)
--foreground
oklch(0 0 0) / oklch(1 0 0)
--card
oklch(1 0 0) / oklch(0.3211 0 0)
--muted
oklch(0.9551 0 0) / oklch(0.2178 0 0)
--muted-foreground
oklch(0.3211 0 0) / oklch(0.8452 0 0)
--border
oklch(0 0 0) / oklch(1 0 0)
--destructive
oklch(0 0 0) / oklch(1 0 0)

Brand & functional (fixed hues)

--primary
oklch(0.6489 0.237 var(--brand-hue))
--primary-foreground
oklch(0 0 0) / oklch(0 0 0)
--primary-text
oklch(0.55 0.12 var(--brand-hue)) / var(--primary)
--secondary
oklch(0.968 0.211 109.7692) / oklch(0.9691 0.2005 109.6228)
--secondary-foreground
oklch(0 0 0)
--accent
oklch(0.5635 0.2408 260.8178) / oklch(0.6755 0.1765 252.2592)
--accent-foreground
oklch(1 0 0) / oklch(0 0 0)

Terminal surfaces (always dark)

--terminal
oklch(0.2 0 0)
--terminal-foreground
oklch(1 0 0)
--terminal-hover
mix(fg 14% → terminal)
--terminal-muted
mix(fg 8% → terminal)

Charts

--chart-1
var(--primary)
--chart-2
var(--secondary)
--chart-3
var(--accent)
--chart-4
oklch(0.7323 0.2492 142.4953)
--chart-5
oklch(0.5931 0.2726 328.3634)

Semantics

Rules that look negotiable but are not

Neutrals stay pure achromatic (no warm/cool grays), shadows stay hard (zero blur, tiny soft layer only at md+), radius stays 0 except the bevel upgrade and small status pills, and the terminal bar is dark in BOTH themes — it reads as a CRT bezel, not a themed surface.

Hard offset shadows

The shadow IS the affordance. Dark mode inverts the shadow color — including the small tiers — or press buttons lose their lift on the pure-black canvas.

2xs
xs
sm
md

Radius law

--radius is 0px; where CSS supports it, corner-shape: bevel upgrades it to 8px. rounded-full is reserved exclusively for small status dots and pills.

radius 0 (bevel where supported) pill the only rounded-full

Glass material

The glass effect: translucent surface + real backdrop blur (14px, saturation 1.35), painted by the shared glass law sheet off the data-jx-effect="blur" stamp and tuned through --jx-glass-* vars. Drawn with an outline instead of a border so the box line never shifts layout. The mobile ToC rail is made of this. (forced-colors: this demo page is exempt from the consumer Canvas map by declaration.)

the blur effect over brand primaries

Beyond sRGB on purpose

Several chroma values intentionally exceed the sRGB gamut (secondary renders as pure #ffff00 after clipping). The neon clip IS the brutalist look. An HSL formulation was tried and rejected: sRGB HSL lightness is hue-dependent and distorts perceived weight; OKLCH keeps it perceptual.

secondary · accent · chart-4
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h