diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e77ca9..12697f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,15 @@ New `tokenColors` rules and `semanticTokenColors` selectors in `themes/_src/base - **Semantic selectors**: `selfParameter` / `clsParameter` (= `this`/`self`/`super`), `magicFunction` (= Python magic methods), `builtinConstant` and `boolean` (= number/boolean colour), `builtinType` (= `type.defaultLibrary`, italic), `lifetime`, `attribute`, `derive` (= Rust lifetime/attribute colours, italic), `formatSpecifier` (= f-string braces), `escapeSequence` (= escapes, bold), `event` (= property). - The pipeline test now checks that everything the v1.2.0 snapshot styled is still styled the same way (additions allowed, drops/restyles fail). +### Changed — Docs (honest science, contributor guide) + +- **README "The Science" rewritten** to match the physics: dominant wavelength (576–611 nm, true of every hex) is distinguished from spectral content (the neutrals still light the blue subpixel — ~23–27 % of their light vs 33 % for white); the rod-impact table is replaced with the CIE 1951 scotopic V′(λ) curve (peak 507 nm); the benefits are stated as what they are — low text luminance (halation), no blue defocus, lower melanopic stimulus, comfort — and "protects dark adaptation" is retracted (reading is cone vision; the neutrals stimulate rods at ~85 % of white per unit luminance; red-only variants remain on the roadmap). Halation is attributed to dim text, not to `#0C0A09` vs `#000000`. +- **Marketplace-first ordering**: pitch → variants table → science → install → settings → display → palette → terminal → extensions → accessibility → FAQ; hero-image slot reserved for the release PR. +- **Accessibility**: WCAG and APCA presented side by side with the verifier's floors; new colour-vision-deficiency section describing the protan/deutan checks (all four variants incl. Roman); "Keywords use weight 450 (lighter bold)" corrected — theme bold is 700, 450 is the recommended `editor.fontWeight` for body text. +- **Recommended settings** add `"editor.fontVariations": true` so a variable font actually renders weight 450; **Display** section gains melanopic / OS warm-shift advice (Night Shift stacks with the palette). +- **FAQ** rewritten ("Why no blue or cyan?", new "Does it protect my night vision?" and "Is it colour-blind safe?"). +- **`CONTRIBUTING.md`** added; the "Building the Themes (Contributors)" section moves there and grows into layout, verifier contract, palette-change and new-variant procedures. `package.json` description no longer claims dark-adaptation protection. + ### Changed — Internal: verifier v2, tests, CI - **Verifier v2** (`scripts/verify-palette.mjs`). New checks alongside the L\* ladder, wavelength band, and key parity: an **APCA Lc** column and per-role floors (body 60 / syntax 40 / special 37 / punctuation 28 / comments 22, overridable per variant via `verify.apcaFloors` — Alone Soft declares a scaled set); **ΔE2000** on every tight ladder gap; a **colour-vision-deficiency** table (Viénot protan/deutan/tritan simulation) over the ten most confusable role pairs, passing on ΔE2000 ≥ 5 or a font-style difference; **ANSI** pairwise ΔE2000 among the eight normal terminal slots (≥ 10); and a **whole-theme wavelength scan** — every chromatic hex in every variant, not just the ten headline roles, must have a dominant wavelength ≥ 575 nm. The perceptual checks were introduced as warnings against the 1.3.1 palette (which missed several — comments Lc 16, ANSI blue/cyan ΔE 5.2, Variables/Parameter under CVD) and are hard failures from 2.0.0. Each check's severity is a one-line `POLICY` entry. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7c1aef6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,76 @@ +# Contributing to Alone + +Thanks for helping. This file covers how the theme is built and verified, how to change the palette or add a variant, and what a pull request needs to pass. + +## Layout + +``` +themes/_src/base.yaml structural skeleton shared by every variant (${token} placeholders) +themes/_src/variants/.yaml per-variant bindings: display, filename, verify, tokens +themes/*.json GENERATED — never edit by hand +themes/_snapshot/ immutable v1.2.0 baseline used by the coverage test +scripts/build-themes.mjs generator CLI (scripts/lib/build.mjs) +scripts/verify-palette.mjs palette verifier + README table renderer +scripts/lib/color.mjs colour math: sRGB/Lab/LCh, WCAG, APCA, ΔE2000, CVD, dominant wavelength +scripts/lib/theme-roles.mjs the syntax-role ladder and CVD pairs the verifier checks +tests/ node:test suites (colour math, generator, pipeline, verifier smoke) +terminal/ Kitty / iTerm2 / Alacritty / Windows Terminal schemes (mirror the ANSI slots) +samples/ demo files for eyeballing every variant +``` + +## Building + +The shipped `themes/*.json` files are **generated**. Don't edit them directly — your edits will be overwritten on the next build. + +- **`themes/_src/base.yaml`** — every key the variants share (shape, scopes, font styles, and any hex that is identical across all variants) lives here as a literal. Every leaf that varies across variants is written as `${token.name}`. A bare `${token}` is replaced by whatever type the variant binds (string, boolean, …); a token inside a longer string is interpolated as text. +- **`themes/_src/variants/.yaml`** — `display`, `filename`, a `verify` block (`isStandard` marks the variant the L\* ladder and README tables come from; `wavelengthBand`; optional `apcaFloors` overrides), and a flat `tokens:` map supplying every `${token.name}` the base references — including the two font-style tokens `style.italic` (`italic` or `""`) and `style.semanticItalic` (`true`/`false`) that Alone Roman binds to "off". The generator errors on unknown tokens and on tokens a variant declares but the base never uses. + +```bash +npm ci +npm run build:themes # regenerate themes/*.json from _src/ +npm run verify # L* ladder, APCA floors, CVD + ANSI ΔE2000, wavelength scan, key parity, README tables +npm run check # both, in sequence +npm test # node --test tests/ +node scripts/verify-palette.mjs --write-readme # re-render the verifier-owned README tables after a palette change +npx @vscode/vsce package # build the VSIX locally +``` + +## The verifier is the contract + +`scripts/verify-palette.mjs` hard-fails on: + +- **Ladder** — the ten syntax roles (Operators → Comments) must stay in descending L\* order on the standard variant, with no adjacent gap under 3 L\* or ΔE2000 under 4. +- **APCA floors** — identifiers ≥ 60, syntax ≥ 40, Special/Strings ≥ 37, punctuation ≥ 28, comments ≥ 22 (Alone Soft carries its own floors in its variant file). +- **CVD** — the role pairs in `scripts/lib/theme-roles.mjs` must stay ΔE2000 ≥ 5 apart after protan and deutan simulation, or differ in font style. Alone Roman has no italic cue, so its pairs must pass on colour alone. +- **ANSI** — the eight normal terminal slots pairwise ΔE2000 ≥ 10. +- **Wavelength** — two parts. The ten ladder roles must pass the variant's declared band (`warm` = no blue/cyan hex). Separately, *every* chromatic hex in the theme — UI chrome, terminal, everything — must have a dominant wavelength ≥ 575 nm (`SCAN_MIN_NM`); neutrals below the chroma threshold are skipped. Note the whole-theme scan does not apply the declared band: a narrower band (a future `red-only` variant, say) constrains the ten roles, not the rest of the theme. +- **Parity** — every variant has exactly the same keys, rules and selectors as every other. +- **README** — the tables between `` markers must match what the palette produces. + +The header of the script explains the policy: since 2.0.0 every check is a hard failure. If you are deliberately moving the palette, demote the affected check to `warn` in the same PR that changes the palette and restore it before merge — do not add environment overrides. + +## Changing the palette + +1. Edit the hex in `themes/_src/variants/.yaml` (or `base.yaml` if it is shared by every variant). +2. `npm run check` — read the verifier output; fix any failure rather than relaxing the threshold. +3. `node scripts/verify-palette.mjs --write-readme` and commit the README diff along with the regenerated `themes/*.json`. +4. If you touched an ANSI slot, mirror it in the four `terminal/` files (the README ANSI table is the reference). +5. Note the change under `## [Unreleased]` in `CHANGELOG.md`. Visible look changes are a minor/major bump; fixes that don't change rendered colours are patch. + +If the README makes a numeric claim that isn't inside a verifier block (blue-subpixel share, rod-stimulation ratios, L\* of a specific hex), recompute it from the new palette before publishing — the verifier can't see prose. + +## Adding a scope or semantic token + +Edit `base.yaml` only. Bind the new rule to an existing role token (`${tokenColors..settings.foreground}` or `${semanticTokenColors..foreground}`, or `${colors.}`) so every variant inherits it and the parity check stays green; a new hex means a new token in every variant file. Add a demo line to the relevant `samples/` file, and extend `tests/pipeline.test.mjs` only if the coverage guarantee itself changes. + +## Adding a variant + +1. Copy the closest existing `themes/_src/variants/*.yaml`, change `display` / `filename` / `verify`, and fill in the `tokens:` block. +2. Add a `contributes.themes` entry in `package.json`, a README blurb under **Theme Variants**, and a CHANGELOG line. +3. `npm run check && npm test`. No `base.yaml` edits are needed unless the variant introduces a new key or scope, in which case every variant must add it. + +## Pull requests + +- Conventional commit prefixes: `feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`. +- `npm run check && npm test` green locally; CI runs the same plus `git diff --exit-code themes/` (committed JSON must be what the build produces) and a `vsce package` dry run on Node 22 and 24. +- Keep generated JSON, README tables and CHANGELOG in the same PR as the palette change that caused them. diff --git a/README.md b/README.md index c34616d..b1d9044 100644 --- a/README.md +++ b/README.md @@ -2,50 +2,75 @@ **A theme for those who thrive coding alone in the dark.** -The ultimate expression of vision science in a coding theme. Protect your eyes as your sessions extend for hours. Your eyes won't hate you. + + +Alone is a warm, low-luminance dark theme for people who code in dark rooms for hours. Every colour in it — syntax, UI chrome, terminal — has a dominant wavelength between 575 and 611 nm: gold, amber, olive, terracotta, dusty rose. There is no blue, no cyan, no purple, and no white text. Foregrounds sit well below the brightness of a typical dark theme, contrast is tuned with APCA rather than maxed out, and a verifier script fails the build if any of that drifts. + +It is not a night-vision instrument (see [What it does not do](#what-it-does-not-do)); it is a comfortable place to spend a long night. --- -## The Philosophy +## Variants -Some of us do our best work in the dark. Late nights, early mornings, blackout curtains drawn. Just you, your code, and the glow of the screen. +| Theme | For | What changes | +| ----- | --- | ------------ | +| **Alone** | The default. A dim-but-not-dark room. | Full palette, italic + bold cues, standard chrome. | +| **Alone Soft** | Pitch-black rooms, light-sensitive eyes. | Every syntax colour ~20 % dimmer, backgrounds lifted a notch, gentler contrast (own APCA floors). | +| **Alone Focused** | Concentration. | Same code colours; UI chrome muted — badges dimmed, sidebar de-emphasised, borders hidden. | +| **Alone Roman** | Astigmatism, or anyone who dislikes italic code. | Standard palette with the italic channel removed; two hexes nudged so the pairs italics used to separate stay separable on colour alone. | -**Alone** is built for these moments. Not just another dark theme—a scientifically engineered environment that protects your dark adaptation, reduces eye strain, and lets you code for hours without fatigue. +Details on each are in [Theme Variants](#theme-variants). --- ## The Science -### Why Most Dark Themes Fail You +The old pitch for this theme was "only wavelengths above 575 nm, so your rods stay dark-adapted." That is a claim about *dominant wavelength*, and it is true — but it is not the same as a claim about the *light*, and it is not where the comfort actually comes from. Here is what the palette really does and why it helps. + +### What "warm-only" means on a screen + +A monitor mixes three primaries. Any colour that is not fully saturated lights all three subpixels, including the blue one, no matter what hue it reads as. Alone's saturated golds emit almost nothing short of 540 nm; its neutrals — the body text, operators, punctuation, comments — necessarily do, because a neutral *is* a mix. -Standard dark themes use blue-heavy syntax highlighting (cyan strings, blue keywords, purple types). This is catastrophic for dark-room work: +| Group (Standard) | Example | Dominant λ | Blue subpixel's share of the light | Rod stimulation per unit luminance, vs white | +| ---------------- | ------- | ---------- | ---------------------------------- | -------------------------------------------- | +| Neutrals (foreground, operators, punctuation, comments) | `#C8B89A` | 577 nm | ~23–27 % (white is 33 %) | ~85 % | +| Ambers (types, functions) | `#C08868` | 579–588 nm | ~11–15 % | ~60 % | +| Saturated golds (numbers, keywords, cursor) | `#C8A040` | 578–581 nm | ~5–6 % | ~55 % | +| Pure red (not in Alone — reserved for future variants) | `#B00000` | 612 nm | ~0 % | a few % | -- **Blue light (450-490nm)** causes maximum rhodopsin bleaching, destroying your dark adaptation -- **Cyan (490-520nm)** hits peak rod cell sensitivity—the receptors you need most in low light -- **High contrast** (white on black) causes halation, especially for astigmatic eyes +Rod figures use the CIE 1951 scotopic curve through a tristimulus approximation and are indicative, not exact. The 2.0.0 retune pulled the bright neutrals' blue-subpixel drive from 37–48 % of full output down to 32–39 % without changing their lightness; it cannot go to zero while they stay readable as neutral text. -Your eyes spend 20-30 minutes building dark adaptation. One bright blue highlight resets the clock. +### The rod curve, for reference -### The Alone Approach +Rod (scotopic) sensitivity V′(λ) peaks at 507 nm, not in the blue: -**Alone** uses only wavelengths above 575nm—amber, orange, gold, olive—the spectrum where rhodopsin absorption drops to near-zero. Your rod cells remain undisturbed. Your cone cells handle the reading. Your dark adaptation survives. +| λ (nm) | Colour | V′(λ) | Where it sits in Alone | +| ------ | ------ | ----- | ---------------------- | +| 450 | blue | 0.46 | no dominant hue here; only the neutrals' blue-subpixel component | +| 480 | blue-cyan | 0.79 | — | +| 507 | blue-green | **1.00** (peak) | — | +| 540 | green | 0.65 | — | +| 560 | yellow-green | 0.33 | — | +| 575–580 | yellow / gold | 0.16–0.12 | Operators, variables, numbers, keywords, types, strings, comments | +| 590 | amber | 0.07 | Functions (588 nm) | +| 610 | orange-red | 0.016 | Special (611 nm), errors (608 nm) | +| 620–650 | red | 0.007–0.0007 | future red-only variants | -| Wavelength | Color | Rod Impact | Our Usage | -| ---------- | ------------ | ----------- | ------------------ | -| 450-490nm | Blue | **Maximum** | ❌ Eliminated | -| 490-520nm | Cyan | **Severe** | ❌ Eliminated | -| 520-560nm | Green | Moderate | ⚠️ Avoided | -| 560-580nm | Yellow-Green | Low | ✅ Strings (olive) | -| 580-620nm | Amber/Orange | Minimal | ✅ Primary palette | -| 620-700nm | Red | Near-zero | ✅ Errors only | +### Where the benefit actually comes from -### Halation Reduction +1. **Low luminance, unsaturated text.** The Standard foreground is L\* 75 — roughly half the luminance of white — and nothing in the syntax ladder is brighter than L\* 81. Glare, halation and contrast-driven fatigue scale with the luminance and edge contrast of the *text*, so this is the single biggest lever. (The background being `#0C0A09` rather than `#000000` — L\* 2.8 vs 0 — is cosmetic; it does not reduce halation. Dim text does.) +2. **No blue defocus.** The eye focuses 450 nm and 620 nm about 1.5–2 dioptres apart; that is why blue text on black shimmers and blooms for astigmatic and uncorrected eyes. Alone's saturated colours span roughly 540–610 nm (≈ 0.5 D of longitudinal chromatic aberration), and the neutrals carry far less blue than white text. This is the most real optical benefit of a warm palette. +3. **Less melanopic stimulation.** The melanopsin (ipRGC) curve peaks near 490 nm, close to the rod curve, so the ratios track the rod column above: the golds deliver about half the melanopic stimulus of white per unit luminance, the neutrals about 85 %. Combined with a lower absolute luminance and an OS warm shift (see [Display recommendations](#display-recommendations)) that adds up. +4. **Comfort.** Warm, low-chroma light at low luminance is simply easier to sit in for hours. That is subjective, and it is fine for it to be — the numbers above are what makes it defensible. -Pure black (#000000) backgrounds cause halation—a glowing halo effect around bright text, especially problematic for astigmatism. **Alone** uses #0C0A09, a near-black with warm undertones that reduces this effect while maintaining the dark aesthetic. +### What it does not do -### Lightness Spacing (L\*) +- It does not preserve rod dark adaptation. Reading is foveal, cone-mediated vision; any screen bright enough to read from will reset full dark adaptation, and the neutrals stimulate rods at ~85 % of white per unit luminance. Recovery takes 30–40 minutes regardless of hue. If you need to keep night vision — telescope, cockpit, bridge — you need a *red-only* palette, which is where the roadmap's Apollo / Max Dark variants sit (see [Future Plans](CHANGELOG.md#future-plans)). +- It does not make the background matter. `#0C0A09` is warm so that the UI chrome does not look cold against the palette; that is all. -On calibrated OLED and miniLED displays, subtle color differences disappear. **Alone** ensures each syntax element has distinct L\* (perceptual lightness) values, computed in CIE L\*a\*b\* against the editor background `#0C0A09`: +### Lightness spacing (L\*) + +On calibrated OLED and mini-LED displays subtle colour differences disappear, so each syntax tier gets its own perceptual lightness, computed in CIE L\*a\*b\* against the editor background `#0C0A09`: @@ -68,6 +93,112 @@ The ladder is monotonic descending. The warm-only palette can't deliver large ga --- +## Installation + +### VS Code Marketplace + +1. Open **Extensions** (Ctrl+Shift+X / Cmd+Shift+X) +2. Search for **"Alone"** +3. Click **Install** +4. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P) +5. Select **Preferences: Color Theme** and choose from: + - **Alone** — The standard theme + - **Alone Soft** — Dimmer variant for pitch-black rooms + - **Alone Focused** — Minimal UI for maximum focus + - **Alone Roman** — Standard palette, no italics (astigmatism / italic-averse) + +### From VSIX + +1. Download the latest `.vsix` from [Releases](https://github.com/crypticpy/alone/releases) +2. In VS Code, open Command Palette (Ctrl+Shift+P / Cmd+Shift+P) +3. Run **Extensions: Install from VSIX...** +4. Select the downloaded `.vsix` file +5. Reload VS Code when prompted + +### From Source + +```bash +git clone https://github.com/crypticpy/alone.git +cd alone && npm ci && npx @vscode/vsce package # produces alone-.vsix +code --install-extension alone-*.vsix +``` + +--- + +## Recommended Settings + +For the full **Alone** experience, add these to your `settings.json`: + +```json +{ + // Font: a variable monospace at a slightly-lighter-than-regular body weight. + // fontVariations:true maps fontWeight onto the font's wght axis, so 450 really + // renders as 450 instead of snapping to the nearest static instance. + "editor.fontFamily": "'JetBrains Mono Variable', 'JetBrains Mono', monospace", + "editor.fontSize": 14, + "editor.fontWeight": "450", + "editor.fontVariations": true, + "editor.fontLigatures": false, + "editor.lineHeight": 1.6, + "editor.letterSpacing": 0.3, + + // Cursor: solid, no blinking (fewer micro-adaptations) + "editor.cursorBlinking": "solid", + "editor.cursorStyle": "block", + "editor.cursorWidth": 2, + + // Brackets: enable colorization (depth maps to dimness) + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": "active", + + // Smooth scrolling + "editor.smoothScrolling": true, + "workbench.list.smoothScrolling": true, + "terminal.integrated.smoothScrolling": true, + + // Reduce visual noise (minimap kept as a dim shape — no rendered characters) + "editor.minimap.enabled": true, + "editor.minimap.renderCharacters": false, + "editor.minimap.scale": 1, + "editor.renderWhitespace": "none", + "editor.renderLineHighlight": "line", + "editor.occurrencesHighlight": "singleFile", + + // Semantic highlighting (recommended — the theme styles Pylance / rust-analyzer / TS server tokens) + "editor.semanticHighlighting.enabled": true, + + // Window: reduce chrome if desired + "window.titleBarStyle": "custom", + "window.autoDetectColorScheme": false +} +``` + +Bold in the theme (keywords, escapes, errors) is CSS `bold` — weight 700. VS Code themes cannot set intermediate weights; `editor.fontWeight` only controls the body text, so with the settings above you get 450 body against 700 keywords. + +--- + +## Display Recommendations + +### OLED + +- **Brightness**: 20–40 % in dark rooms +- Enable the pixel brightness limiter / ABL if the panel offers one +- The palette's L\* 75 foreground stays clear at these brightnesses; you should not need to push it higher + +### Mini LED + +- **Brightness**: 30–50 % in dark rooms +- **Local dimming**: Medium (not Maximum) to reduce blooming +- **Contrast**: 80–90 % to prevent halation + +### Both + +- **Night Shift / Night Light / f.lux**: enable, and set it warm (3000–4000 K). Alone stacks well with an OS warm shift: it removes still more of the neutrals' blue-subpixel light, and because nothing in the palette depends on a cool hue, the theme keeps its contrast where a blue-heavy theme collapses toward gray. +- **True Tone / auto-brightness**: disable for consistency. +- The screen is still the brightest thing in a dark room. If your goal is less melanopic light in the evening, the biggest levers are the display's absolute brightness and the OS warm shift; the theme helps on top of those, not instead of them. + +--- + ## Color Palette ### Syntax Colors @@ -120,61 +251,57 @@ Six warm variants for bracket colorization, ordered by perceptual lightness so * --- -## Installation +## Theme Variants -### VS Code Marketplace +**Alone** is the headline family member — tuned for the mesopic range, where both rods and cones contribute, typical of a dim-but-not-dark room. The three siblings are positioned around it; further along the dark-adaptation continuum the family has room to grow (see [Future Plans](CHANGELOG.md#future-plans)). -1. Open **Extensions** (Ctrl+Shift+X / Cmd+Shift+X) -2. Search for **"Alone"** -3. Click **Install** -4. Open Command Palette (Ctrl+Shift+P / Cmd+Shift+P) -5. Select **Preferences: Color Theme** and choose from: - - **Alone** — The standard theme - - **Alone Soft** — Dimmer variant for extreme dark adaptation - - **Alone Focused** — Minimal UI for maximum focus - - **Alone Roman** — Standard palette, no italics (astigmatism / italic-averse) - -### From VSIX (Recommended for Manual Install) +### Alone (Standard) -1. Download the latest `.vsix` from [Releases](https://github.com/crypticpy/alone/releases) -2. In VS Code, open Command Palette (Ctrl+Shift+P / Cmd+Shift+P) -3. Run **Extensions: Install from VSIX...** -4. Select the downloaded `.vsix` file -5. Reload VS Code when prompted +The full-featured theme with balanced contrast for extended coding sessions. Default starting point for most users. -### From Source +### Alone Soft -```bash -# Clone the repository -git clone https://github.com/crypticpy/alone.git +For pitch-black rooms and light-sensitive eyes. All syntax colours reduced ~20 % in brightness, backgrounds slightly lifted to reduce contrast. Verified against its own, lower APCA floors. -# Copy to VS Code extensions -cp -r alone ~/.vscode/extensions/ +### Alone Focused -# Restart VS Code -``` +For concentration. UI chrome is muted — activity-bar badges dimmed, sidebar de-emphasised, borders hidden. Syntax highlighting unchanged. Your code takes centre stage. ---- +### Alone Roman -## Theme Variants +The Standard palette with **no italics** — for astigmatic readers who find slanted monospace edges fringe, or anyone who simply dislikes italic code. Every rule that Standard sets in italic (comments, strings, docstrings, regex, decorators, interfaces, type parameters, namespaces, `*.defaultLibrary`, `*.async`, `this`/`self`) is upright here; the only italic left is Markdown `*emphasis*`, which is the document's own formatting. Bold keywords/escapes/errors are unchanged. Two hexes differ from Standard so the pairs that Standard tells apart with italics stay separable on colour alone under red-green colour-vision deficiency: strings `#9A8B60 → #9C8B4A` and `*.defaultLibrary` `#B08C50 → #AA884C` (both verified ΔE2000 ≥ 5 after protan/deutan simulation). What you give up is the italic-only distinctions — interfaces look like classes, library calls like local ones, async like sync. -**Alone** is the headline family member — a **mesopic**-light theme tuned for the intermediate range where both rod and cone receptors contribute, typical of a dim-but-not-dark room. The three siblings below are positioned around it; further along the dark-adaptation continuum, the family has room to grow (see [Future Plans](CHANGELOG.md#future-plans)). +--- -### Alone (Standard) — _mesopic_ +## Terminal Themes -The full-featured theme with balanced contrast for extended coding sessions. Default starting point for most users. +Matching terminal themes are included in the `terminal/` directory: -### Alone Soft +| Terminal | File | Installation | +| -------------------- | -------------------------------------- | ----------------------------------------------------------------- | +| **Kitty** | `terminal/alone.conf` | Copy to `~/.config/kitty/themes/` and `include themes/alone.conf` | +| **iTerm2** | `terminal/alone.itermcolors` | Preferences → Profiles → Colors → Import | +| **Alacritty** | `terminal/alone.toml` | Import in your `alacritty.toml` config | +| **Windows Terminal** | `terminal/alone-windows-terminal.json` | Add scheme to `settings.json` | -For **extreme dark adaptation**. All syntax colors reduced ~20% brightness, backgrounds slightly lifted to reduce contrast. Perfect for pitch-black rooms or users with high light sensitivity. +All terminal themes use the same warm colour palette. The blue and cyan slots carry no short-wavelength dominant hue: blue is a warm gray (`#9C948E`) and cyan is the terracotta used for functions (`#C08868`) — two hues a red-green-deficient reader can still tell apart (ΔE2000 ≥ 10 across all eight normal slots), instead of the two near-identical grays that shipped before 2.0.0. Programs that print blue-on-black (`ls`, `grep`, `man`) stay readable (blue Lc 45 against the terminal background). -### Alone Focused +The sixteen ANSI slots (as shipped in the VS Code theme and mirrored in the terminal files): -For **maximum concentration**. UI chrome is muted—activity bar badges dimmed, sidebar de-emphasized, borders hidden. Syntax highlighting unchanged. Your code takes center stage. + -### Alone Roman +| Slot | Hex | L\* | Bright slot | Hex | L\* | +| ------- | --------- | --- | ------------- | --------- | --- | +| Black | `#0C0A09` | 3 | BrightBlack | `#6E665B` | 44 | +| Red | `#B85450` | 48 | BrightRed | `#D46A66` | 57 | +| Green | `#9A8B60` | 58 | BrightGreen | `#C4B078` | 72 | +| Yellow | `#D4A048` | 69 | BrightYellow | `#E8B850` | 77 | +| Blue | `#9C948E` | 62 | BrightBlue | `#B2AAA3` | 70 | +| Magenta | `#A87878` | 55 | BrightMagenta | `#C89088` | 65 | +| Cyan | `#C08868` | 61 | BrightCyan | `#CEA284` | 70 | +| White | `#C8B89A` | 75 | BrightWhite | `#EDDCC4` | 89 | -The Standard palette with **no italics** — for astigmatic readers who find slanted monospace edges fringe, or anyone who simply dislikes italic code. Every rule that Standard sets in italic (comments, strings, docstrings, regex, decorators, interfaces, type parameters, namespaces, `*.defaultLibrary`, `*.async`, `this`/`self`) is upright here; the only italic left is Markdown `*emphasis*`, which is the document's own formatting. Bold keywords/escapes/errors are unchanged. Two hexes differ from Standard so the pairs that Standard tells apart with italics stay separable on colour alone under red-green colour-vision deficiency: strings `#9A8B60 → #9C8B4A` and `*.defaultLibrary` `#B08C50 → #AA884C` (both verified ΔE2000 ≥ 5 after protan/deutan simulation). What you give up is the italic-only distinctions — interfaces look like classes, library calls like local ones, async like sync. + --- @@ -225,131 +352,31 @@ Add to your `settings.json` for themed TODO highlights: --- -## Recommended Settings - -For the full **Alone** experience, add these to your `settings.json`: - -```json -{ - // Font: JetBrains Mono Variable with lighter weight (reduces halation for astigmatism) - "editor.fontFamily": "'JetBrains Mono Variable', 'JetBrains Mono', monospace", - "editor.fontSize": 14, - "editor.fontWeight": "450", - "editor.fontLigatures": false, - "editor.lineHeight": 1.6, - "editor.letterSpacing": 0.3, - - // Cursor: Solid, no blinking (reduces micro-adaptations) - "editor.cursorBlinking": "solid", - "editor.cursorStyle": "block", - "editor.cursorWidth": 2, - - // Brackets: Enable colorization - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": "active", - - // Smooth scrolling - "editor.smoothScrolling": true, - "workbench.list.smoothScrolling": true, - "terminal.integrated.smoothScrolling": true, - - // Reduce visual noise - "editor.minimap.enabled": true, - "editor.minimap.renderCharacters": false, - "editor.minimap.scale": 1, - "editor.renderWhitespace": "none", - "editor.renderLineHighlight": "line", - "editor.occurrencesHighlight": "singleFile", - - // Semantic highlighting (recommended) - "editor.semanticHighlighting.enabled": true, - - // Window: Reduce chrome if desired - "window.titleBarStyle": "custom", - "window.autoDetectColorScheme": false -} -``` - ---- - -## Display Recommendations - -### OLED Displays - -- **Brightness**: 20-40% in dark rooms -- Enable pixel brightness limiter if available -- **Alone** uses #0C0A09 (not pure black) to prevent harsh pixel edges - -### Mini LED Displays - -- **Brightness**: 30-50% in dark rooms -- **Local dimming**: Medium (not Maximum) to reduce blooming -- **Contrast**: 80-90% to prevent halation - -### Both Display Types - -- **Color Temperature**: 3000-4000K at night (if OS supports) -- **True Tone/Auto-Brightness**: Disable for consistency -- **Night Shift/Blue Light Filter**: Enable and set to maximum warmth - ---- - -## Terminal Themes - -Matching terminal themes are included in the `terminal/` directory: - -| Terminal | File | Installation | -| -------------------- | -------------------------------------- | ----------------------------------------------------------------- | -| **Kitty** | `terminal/alone.conf` | Copy to `~/.config/kitty/themes/` and `include themes/alone.conf` | -| **iTerm2** | `terminal/alone.itermcolors` | Preferences → Profiles → Colors → Import | -| **Alacritty** | `terminal/alone.toml` | Import in your `alacritty.toml` config | -| **Windows Terminal** | `terminal/alone-windows-terminal.json` | Add scheme to `settings.json` | - -All terminal themes use the same warm color palette. The blue and cyan slots carry no short-wavelength light: blue is a warm gray (`#9C948E`) and cyan is the terracotta used for functions (`#C08868`) — two hues a red-green-deficient reader can still tell apart (ΔE2000 ≥ 10 across all eight normal slots), instead of the two near-identical grays that shipped before 2.0.0. Programs that print blue-on-black (`ls`, `grep`, `man`) stay readable (blue Lc 45 against the terminal background). - -The sixteen ANSI slots (as shipped in the VS Code theme and mirrored in the terminal files): - - - -| Slot | Hex | L\* | Bright slot | Hex | L\* | -| ------- | --------- | --- | ------------- | --------- | --- | -| Black | `#0C0A09` | 3 | BrightBlack | `#6E665B` | 44 | -| Red | `#B85450` | 48 | BrightRed | `#D46A66` | 57 | -| Green | `#9A8B60` | 58 | BrightGreen | `#C4B078` | 72 | -| Yellow | `#D4A048` | 69 | BrightYellow | `#E8B850` | 77 | -| Blue | `#9C948E` | 62 | BrightBlue | `#B2AAA3` | 70 | -| Magenta | `#A87878` | 55 | BrightMagenta | `#C89088` | 65 | -| Cyan | `#C08868` | 61 | BrightCyan | `#CEA284` | 70 | -| White | `#C8B89A` | 75 | BrightWhite | `#EDDCC4` | 89 | - - - ---- - ## Language Support **Alone** includes optimized highlighting for: - JavaScript / TypeScript / JSX / TSX -- Python -- Rust +- Python (incl. Pylance semantic tokens: `self`/`cls`, magic methods, builtins) +- Rust (incl. rust-analyzer: lifetimes, attributes, derives, macros) - Go - HTML / CSS / SCSS - JSON / YAML / TOML - Markdown - Shell / Bash - SQL +- Diffs and log files - And more via semantic highlighting -See `samples/` directory for demo files showcasing syntax highlighting. +See the `samples/` directory for demo files showcasing syntax highlighting. --- ## Accessibility -### Contrast Ratios (WCAG) +### Contrast (WCAG and APCA) -Key contrast ratios against the editor background (`#0C0A09`): +Key contrast values against the editor background (`#0C0A09`), WCAG 2.x ratio and [APCA](https://github.com/Myndex/SAPC-APCA) (0.0.98G-4) Lc side by side: @@ -366,17 +393,21 @@ Key contrast ratios against the editor background (`#0C0A09`): -Computed against the editor background (`#0C0A09`) using the WCAG 2.x contrast formula. Comments intentionally use lower contrast to de-emphasize them. APCA Lc is the [APCA](https://github.com/Myndex/SAPC-APCA) (0.0.98G-4) lightness-contrast value; ≥ 60 is the body-text target, ≥ 45 large text, ≥ 30 the floor for non-essential text. All primary code elements meet WCAG AA for normal text; five of eight clear AAA. This table is rendered by `node scripts/verify-palette.mjs --write-readme`. +APCA Lc ≥ 60 is the body-text target, ≥ 45 large text, ≥ 30 the floor for non-essential text; comments intentionally sit lower to de-emphasise them. All primary code elements meet WCAG AA for normal text; five of eight clear AAA. The verifier enforces per-role APCA floors (identifiers ≥ 60, syntax ≥ 40, Special/Strings ≥ 37, punctuation ≥ 28, comments ≥ 22; Alone Soft has its own lower floors) as hard failures, and this table is rendered by `node scripts/verify-palette.mjs --write-readme`. + +### Colour-vision deficiency -### Astigmatism Considerations +A warm-only palette lives mostly on the red-green axis, which is exactly what protanopes and deuteranopes lose. So the verifier simulates protan and deutan vision for every variant and requires that ten role pairs that carry meaning (types vs functions, functions vs strings, keywords vs types, numbers vs keywords, strings vs special, errors vs functions/special, escapes vs variables, `defaultLibrary` vs types, variables vs parameters) stay at least ΔE2000 5 apart after simulation — or differ in font style. Alone Roman has no italic cue, so it passes on colour alone (that is why two of its hexes differ from Standard). The eight *normal* ANSI slots (0–7) must stay ≥ ΔE2000 10 apart pairwise; the bright slots (8–15) are deliberately the same hues one step lighter, so they are not held to that bound against their dim twins. All four variants pass; the exact numbers are in the verifier's sections 4 and 5. -- Near-black background (#0C0A09) reduces halation vs pure black -- Keywords use weight 450 (lighter bold) to reduce fringing -- Warm desaturated palette minimizes chromatic aberration +### Astigmatism + +- Body text at L\* 75 rather than white, and nothing brighter than L\* 81 in the syntax ladder — halation scales with text luminance +- No blue, so no blue defocus / chromatic fringing on dark backgrounds +- Body weight 450 via `editor.fontWeight` + `editor.fontVariations` (see settings); bold stays a plain 700 - Cursor uses mid-tier gold (`#D4A048`) rather than the brightest palette hex — a small bright point on near-black is a halation hotspot, so we keep it dimmer -- **Alone Soft** variant available for users needing even lower contrast +- **Alone Roman** removes the italic channel; **Alone Soft** lowers contrast further -### Italic Fringing Tradeoff +### Italics Italics provide cheap differentiation (strings, comments, interfaces, type parameters, `defaultLibrary`, regex) within the warm palette. But slanted edges in a monospace font can _increase_ fringing for astigmatic readers. If italics give you trouble, pick **Alone Roman** — the same palette with the italic channel removed and two hexes adjusted so nothing that italics used to separate collapses. If you want to keep Soft or Focused and only drop a few italics, VS Code's per-theme overrides still work: @@ -406,50 +437,37 @@ Italics provide cheap differentiation (strings, comments, interfaces, type param ### Why no blue or cyan? -Blue (450-490nm) and cyan (490-520nm) light causes maximum disruption to dark-adapted vision. These wavelengths bleach rhodopsin, the photopigment in your rod cells, requiring 20-30 minutes to regenerate. **Alone** uses only wavelengths above 575nm. +Two real reasons and one that used to be claimed. Real: blue text on a dark background defocuses relative to the rest of the line (chromatic aberration) and blooms for astigmatic eyes, and short-wavelength light drives melanopsin harder per unit brightness. Claimed: "blue bleaches rhodopsin and destroys dark adaptation" — true of the light, but reading is cone vision on a screen that already resets dark adaptation; see [What it does not do](#what-it-does-not-do). + +### Does it protect my night vision? + +No. It stimulates rods less than a white-on-black theme at the same brightness (the golds about half as much, the neutrals about 85 % as much), and it is dimmer overall, but that is a comfort gain, not preserved dark adaptation. Red-only variants for that use case are on the roadmap. ### Why italic for strings and comments? -Font style provides differentiation without requiring additional colors. Italics signal "different voice"—comments are authorial notes, strings are literal data. This lets us maintain distinguishability within our constrained warm palette. +Font style provides differentiation without requiring additional colours. Italics signal "different voice" — comments are authorial notes, strings are literal data. This lets us maintain distinguishability within a constrained warm palette. If italics don't work for you, use Alone Roman. ### Why bold for keywords? -Keywords are control flow—the skeletal structure of your program. Bold weight provides immediate visual anchoring without requiring a brighter or more saturated color that could cause eye strain. +Keywords are control flow — the skeletal structure of your program. Bold weight provides immediate visual anchoring without requiring a brighter or more saturated colour. ### Is this good for astigmatism? -Yes. The near-black background (#0C0A09) reduces halation compared to pure black. The warm, desaturated palette minimizes chromatic aberration. The constrained contrast ratio prevents the "blooming" effect common with bright-on-dark themes. +Usually. Low text luminance and no blue are the two things that matter most for halation and fringing; the Roman variant removes italics as well. If you still see fringing, try Alone Soft, and drop the display brightness before pushing the theme brighter. ### Can I use this in a lit room? -**Alone** is optimized for dark environments. In lit rooms, you may want higher contrast. That said, many users find it comfortable in low ambient light conditions. - ---- - -## Building the Themes (Contributors) - -The shipped `themes/*.json` files are **generated**. Don't edit them directly — your edits will be overwritten on the next build. +**Alone** is tuned for dark environments. In a bright room you may want higher contrast; the Standard variant is usually still comfortable in low ambient light. -The source of truth lives under `themes/_src/`: +### Is it colour-blind safe? -- **`themes/_src/base.yaml`** — the structural skeleton. Every key the variants share (shape, scopes, font styles, and any hex that happens to be identical across all variants) lives here as a literal. Every leaf that varies across variants is written as `${token.name}`. -- **`themes/_src/variants/.yaml`** — per-variant bindings: `display`, `filename`, a `verify` block (which wavelength band the variant should pass and whether it's the "standard" used for the L\* ladder / README WCAG checks), and a `tokens:` block supplying the hex/alpha values for that variant's `${token.name}` references — plus the two font-style tokens `style.italic` (`italic` or `""`) and `style.semanticItalic` (`true`/`false`) that Alone Roman binds to "off". +For red-green deficiency, yes, and it is enforced: every meaning-carrying colour pair is checked under simulated protanopia and deuteranopia in all four variants (ΔE2000 ≥ 5 or a font-style difference), and the eight normal terminal slots stay ≥ ΔE2000 10 apart. Tritanopia is *not* simulated or enforced — a warm palette has little blue-yellow content for a tritanope to lose, but that also means some pairs (Types vs Functions, for example) sit closer under tritan simulation than the red-green threshold, and the bright ANSI slots are not checked against each other at all. If you have tritanopia, treat the theme as untested rather than verified. -The scripts: - -```bash -npm run build:themes # regenerate themes/*.json from _src/ -npm run verify # L* ladder, APCA floors, CVD + ANSI ΔE2000, wavelength scan, key parity, README tables -npm run check # both, in sequence -npm test # unit tests (colour math, generator rules) + pipeline tests (determinism, snapshot parity) -node scripts/verify-palette.mjs --write-readme # re-render the verifier-owned README tables after a palette change -``` - -Every numeric table in this README that sits between `` markers is rendered by the verifier from the standard variant; edit the palette, run `--write-readme`, and commit the result — the default `verify` fails on drift. CI (`.github/workflows/ci.yml`) runs build → verify → test → `vsce package` on every push and pull request. +--- -To add a new variant: copy an existing variants file, change `display` / `filename` / `verify`, fill in the `tokens:` block with the new palette, then `npm run check`. No edits to `base.yaml` are needed unless the new variant introduces a new key or scope (in which case all variants must add it — the parity check enforces this). +## Contributing -To change the **shape** (add a scope, add a colors key, reorder tokenColors entries) edit `base.yaml`; the change applies uniformly to every variant. +The shipped `themes/*.json` files are **generated** from `themes/_src/`; don't edit them directly. Build, verify, test and variant-authoring instructions are in [CONTRIBUTING.md](CONTRIBUTING.md). Every numeric table in this README that sits between `` markers is rendered by the verifier — change the palette, run `node scripts/verify-palette.mjs --write-readme`, and commit the result. --- @@ -467,8 +485,8 @@ MIT License. See [LICENSE](LICENSE) for details. ## Acknowledgments -- Vision science research from the National Park Service (dark adaptation studies) -- Rhodopsin absorption spectra from photoreceptor biology literature +- CIE 1951 scotopic luminosity function and the CIE S 026 melanopic action spectrum +- APCA (Myndex) for the lightness-contrast model; Viénot, Brettel & Mollon (1999) for the CVD simulation matrices - The late-night coding community who inspired this work --- diff --git a/package.json b/package.json index 17e7c62..6645165 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "alone", "displayName": "Alone", - "description": "A theme for those who thrive coding alone in the dark. The ultimate expression of vision science—protect your dark adaptation during extended sessions.", + "description": "A theme for those who thrive coding alone in the dark. Warm, low-luminance, no blue or cyan anywhere — every colour verified for contrast (APCA) and colour-vision deficiency. Four variants: Alone, Soft, Focused, Roman.", "version": "2.0.0", "publisher": "crypticpy", "author": {