Rename the v2 spec docs to drop the -v2 suffix and make them the authoritative source of truth. Slim GEMINI.md to a pointer and update CLAUDE.md, AGENTS.md, README.md, INSTALL.md to v2 semantics and the current 15-app target list.
13 KiB
Apex Neon — Dark Theme Specification
Apex Neon is a high-contrast dark theme for terminals, editors, and system UI. It is a rule system, not a palette. Color carries meaning, not decoration.
Thesis. A cold neon HUD on deep black. Hue carries meaning, sparsely. Red = danger. Violet = attention. Both are absent from code, so they always strike. The forest (syntax) is loud and typed by grammatical role so the hunter navigates fast — but the predator and the crosshair stay the only red and violet things in it.
0. What changed from v1
| v1 problem | v2 fix |
|---|---|
| Red overloaded — meant self/cursor and danger simultaneously | Hue split: red = danger only, violet = attention (cursor/search/pairs) |
color4 ≡ color6 and color12 ≡ color14 (cyan/blue collision) |
Blue split into a real channel (#2e6bff / #5b8cff) |
| Comments double-defined (Stealth and Muted) | Comments = Ghost; Stealth = ignored text only |
Backwards bright-red (#ff8899 softer than base — "escalation" read calmer) |
#ff3b3b — hotter and truer red |
| No diff/git colors | Figure/ground diff tints added |
| No syntax model | Role-typed syntax across all languages |
#050505 near-pure black (halation, crushed surfaces) |
#070709 — deep, cool-tinted, halation-safe |
1. Semantic Spine — five hues, zero overlap
The core invariant. Every color does exactly one job.
| Hue | Meaning | In code buffer? | Temp |
|---|---|---|---|
| Red | Danger — error, critical, destructive | ❌ never | warm |
| Violet | Attention — cursor, search, matched pairs | ❌ never | cool |
| Cyan | Information / actions (functions, bang-macros) | ✅ | cool |
| Blue | Kinds (types, storage, attributes) | ✅ | cool |
| Green | Success / values (strings, numbers) | ✅ | cool-warm |
| Amber | Warning — UI only | ❌ never | warm |
Keystone rule. Red, violet, and amber never appear inside a code buffer. When an error (red), the cursor/search (violet), or a warning (amber) fires, it pops against a field that contains none of those hues — however loud the syntax is.
2. Operating Principles
- Saturation budget. Neon is for signal. Body text is near-neutral. Striking comes from restraint against contrast — not coverage.
- Figure / ground. A bright hue marks the one thing (cursor, error text); a dim hue field marks the set (search hits, error region, diff block). Applied to both red and violet.
- Hue rotates for severity.
info → warn → error → criticalrotates hue; it never ramps lightness within a single hue (pre-attentively unreadable). - Loudness is allocated by grammatical role, not sprayed. Code can be vivid; uniform saturation is noise. Each grammatical class owns one hue family.
- Pre-attentive bandwidth is ~5–7 categories. The limit is the feature: scarcity is what makes a signal pop. The display is not the constraint; the visual cortex is.
3. Neutrals — the readable 80%
Anchored at #070709. Faint cool bias (B ≳ R) reinforces the cold scan and makes
warm signals pop by complementary contrast.
| Hex | Name | Role |
|---|---|---|
#070709 |
Void | Main background, terminal background |
#101218 |
Hull | Inputs, inactive tabs, widgets |
#181b22 |
Plating | Panels, separators, scroll track |
#262a34 |
Seam | Visible borders, line-highlight background |
#383d49 |
Stealth | Ignored / suppressed text (sub-threshold by design) |
#8b909c |
Ghost | Comments, subtitles, muted labels |
#e6e8ec |
Stark | Default body text (not pure white — fatigue / halation) |
#ffffff |
Flare | Extreme highlight only |
Rule. Depth comes from contrast, not brightness. Nothing load-bearing ever sits at Stealth.
4. Signal Layer — figure / ground
DANGER (red — warm, alarm)
#ff0044 Razor Red error figure, destructive action, active alarm
#1f0a12 Ember Field error region background (+ diff-delete background)
#ff3b3b Bright Red ANSI color9 / escalation
ATTENTION (violet — cool, scanner; underline-shaped)
#d68fff Focus Max cursor — the crosshair (the single hottest mark)
#b14dff Focus Active current search match, matched bracket / pair
#3a1a4d Focus Field other search matches, selection background
INFORMATION
#00eaff Electric Cyan info / links
#00ff99 Acid Green success / diff-add
#ffb700 Amber warning / diff-change (UI only — never in buffer)
#9d00ff Violet (deep) reserved special / root (ANSI color5)
5. Interaction & State
| Element | Color | Form | Notes |
|---|---|---|---|
| Cursor | #d68fff Focus Max |
underline | Sits below a search match's bg+fg — all three stay visible. |
| Current search match | #b14dff Focus Active |
fg | The one match you are on. |
| Other search matches | #e6e8ec on #3a1a4d |
field | The set; dim violet ground. |
| Selection | #e6e8ec on #3a1a4d |
field | "Region scanned." fg preserved (no black-on-red). |
| Matched bracket / pair | #b14dff |
fg | Attention, not syntax — same act as search. |
| Active line | neutral bg #101218 |
— | Line number in #d68fff is the crosshair; near-zero red coverage. |
| Active border | #ff0044 |
edge | Focused window/element. |
Known overlap. Cursor underline (violet, straight) and error undercurl (red, wavy) share the bottom-line layer. On an error token under the cursor they coexist — distinguishable by shape and hue, but visually busy. Accepted, not a surprise.
6. Severity Ladder — rotates hue, never a red ramp
info warn error critical
#00eaff → #ffb700 → #ff0044 → figure on ground: #ff0044 on #1f0a12
cyan amber red the whole region lights red
Critical does not get a new hue (rotating past red collides with amber or violet). It escalates via field — error-red figure on a dark-red ground. This is the only place red becomes a background. Maximum alarm, maximally rare.
7. Syntax — loud, typed by grammatical role
Model: blue = kinds · cyan = actions · green = values · neutral = connective tissue · bold/bright = navigation. Roles are language-invariant (see §10).
Rust reference map
| Token | Hex | Family / weight |
|---|---|---|
| Comment | #8b909c italic |
neutral — not a target |
| Punctuation / delimiter | #aab0bc |
neutral, lifted |
| Operator | #c2c8d4 |
neutral |
| Variable / param | #e6e8ec |
Stark body (everywhere = calm) |
| Property / field | #cdd4e3 |
dim Stark |
| Type / struct / enum / trait | #7db4ff |
blue — kinds |
Storage / modifier (pub mut async) |
#2e6bff bold |
blue, heavier |
Attribute (#[derive]) |
#2e6bff italic |
blue — declarative |
| Function def / call | #5af3ff bold |
cyan — actions |
Bang-macro (println!) |
#5af3ff bold italic |
cyan — action + ceremony |
self / builtin |
#8fd6ff italic |
cyan, dim |
| String | #2bffb2 |
green — values |
| Char / escape | #7dffd0 |
green, brighter |
| Number / bool / const | #37dba0 |
green, cooler |
Navigation — split static vs dynamic
| Kind | Examples | Color |
|---|---|---|
| Static control flow | if return break continue match, \begin \end, goto |
#5b8cff bold bright-blue — trail markers |
| Dynamic pairing | bracket-under-cursor partner, \begin↔\end, jump target |
#b14dff violet — this is attention, not syntax |
8. Diff / Git
Dark tinted grounds, neon figures. Never full-saturation blocks.
| State | fg | bg |
|---|---|---|
| Add | #00ff99 |
#0a1f16 |
| Change | #ffb700 |
#1f1a0a |
| Delete | #ff0044 |
#1f0a12 ← reused Ember Field |
9. Terminal ANSI Table (16-color fallback bank)
Truecolor-first: design in exact hex everywhere the app emits 24-bit. This bank is the floor for anything emitting raw 16-color codes. Skip the 256-color cube entirely.
Normal Bank (0–7)
| Slot | Name | Hex | Meaning |
|---|---|---|---|
| color0 | Black | #070709 |
Background |
| color1 | Red | #ff0044 |
Danger / error / destructive |
| color2 | Green | #00ff99 |
Success |
| color3 | Yellow | #ffb700 |
Warning |
| color4 | Blue | #2e6bff |
Kinds / structural (real blue) |
| color5 | Magenta | #9d00ff |
Special / root |
| color6 | Cyan | #00eaff |
Info / actions |
| color7 | White | #e6e8ec |
Default text |
Bright Bank (8–15)
| Slot | Name | Hex | Meaning |
|---|---|---|---|
| color8 | BrBlack | #383d49 |
Muted / suggestions |
| color9 | BrRed | #ff3b3b |
Escalation / alert (hotter than base) |
| color10 | BrGreen | #2bffb2 |
Active success / values |
| color11 | BrYellow | #ffd24d |
Urgent warning |
| color12 | BrBlue | #5b8cff |
Active flow nav / focus highlight |
| color13 | BrMagenta | #c84dff |
Elevated attention state |
| color14 | BrCyan | #5af3ff |
Active tech signal / functions |
| color15 | BrWhite | #ffffff |
Extreme highlight only |
Multiplexer warning. tmux silently downsamples truecolor to the 256-cube if misconfigured — which collapses the figure/ground tints. Assert passthrough:
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*:RGB"
# verify the chain carries 24-bit (run inside and outside tmux — should not shift)
printf '\033[38;2;255;0;68mtruecolor\033[0m\n'
Neovim additionally requires vim.o.termguicolors = true.
10. Cross-Language Role Mapping
Color follows roles, not tokens. Surface syntax differs across languages; the deep grammar does not. One palette, every grammar — more languages means more query mappings to maintain, not more colors.
| Role → hue | Rust | Python | TypeScript | Go | Java | Bash/sh | Nushell |
|---|---|---|---|---|---|---|---|
| Kinds (blue) | struct trait |
class, hints | interface type |
struct interface |
class enum |
— (untyped) | int record table |
| Actions (cyan) | fn / m! |
def, methods |
fn, arrow, methods | func |
methods | commands, echo |
def, commands |
| Values (green) | str / num | str / f-str / num | str / template / num | literals | literals | quoted str | literals / records |
| Declarative (blue italic) | #[attr] |
@decorator |
@Decorator |
struct tags | @Override |
— | — |
| Flow nav (bold blue) | if match |
if for try |
if switch |
if for go defer |
if switch |
if/fi for/done case |
if/else for |
| Connective (neutral) | + | :: |
+ . |
+ . => |
+ . := |
+ . |
| && $ > |
| $ ; |
Per-language judgment calls (handle with weight/style, never a new hue)
- Family skew. TypeScript is type-saturated (blue-heavy); bash is command-saturated (cyan-heavy). Resolve with brightness/weight tiers inside the family (e.g. dim built-in types, bright user types).
- Role-ambiguous constructs. Bash
$VAR/${...}/$(...)expansion, Python f-strings / TS template literals (green string containing live code), Nushell pipelines|(connective but navigationally load-bearing). Map each onto an existing family with a weight/style tweak per-language. - Identity pronouns.
self/this/$→ dim cyan italic everywhere.
11. Accessibility Contract
- Body text
#e6e8econ Void#070709— high contrast, low saturation. The resting surface. (≈ 16:1, well above WCAG AAA.) - Comments
#8b909con Void — readable (≈ 6.5:1, AA for normal text). - Stealth
#383d49on Void — sub-threshold by design; ignored text only, never load-bearing. - No neon on neon. Saturated signal colors are never placed on saturated backgrounds; signal always sits on a neutral or dim field.
- Verify exact ratios per render target; near-black + neon halation is panel-dependent and only confirmable on the actual display.
12. Rules with Teeth
- Red is danger. Violet is attention. Neither ever appears inside code.
- Cyan is information and action. Never errors, never destructive.
- Amber is warning — UI only. It never enters a buffer.
- Bright = escalation. If nothing changed, don't use a bright variant.
- Backgrounds stay dark. Text and state carry the signal.
- One color, one meaning. If a color has no semantic reason, it's wrong.
- Loud is earned by role. Vivid syntax is fine; uniform saturation is noise.
- Scarcity is the weapon. A signal only carries information when it is rare.
Apex Neon is not a palette. It's a rule system with teeth.
Open calibrations (verify on a real buffer — not on paper)
- Cyan budget in dense code. Functions, bang-macros,
self, and info/links all live in cyan. A function-heavy file may read as a cyan wash. If so, drop functions to mid-cyan and reserve bright#5af3fffor sparse use. - Blue vs cyan separability.
#2e6bff(kinds) vs#5af3ff(actions) are both cool and adjacent. Types-vs-functions is a constant distinction; confirm they split cleanly on#070709first. - Green double duty. Values (
#2bffb2) and success (#00ff99) are close. Low collision risk (success rarely renders inside code), but noted.