Crypt stone
ground
#17171c#f4eee2
Background
The cold slab on which everything rests.
✠ A reliquary of materials ✠
A gothic, arcane colour scheme for Sublime Text, VS Code and more.
Every colour is born from an object: blood, bone, stone, amber, verdigris.
Concept
Cremisi is a cabinet of materials: every hue comes from a real substance, desaturated, earthy, mineral.
The backbone never changes. Blood is the protagonist, bone is the text, stone is the ground. Everything else revolves around these three.
Its colours are wax, resin and oxidised copper, with two cold notes: lapis, ground for illuminated missals, and verdigris, the patina on bronze doors.
#e2586a#e9e1d1#17171cThe dark ground is cold slate grey, the stone of a crypt.
Keywords, control flow, booleans and accents all speak with the voice of blood.
Lapis blue for functions and keys, verdigris for properties. Every other hue is warm or mineral.
Desaturated, warm, earthy and mineral colours, drawn from wax, resin, copper and stone.
Palette
Sixteen materials with the same names in both variants: only the values change. Click a colour to copy its hex.
ground
#17171c#f4eee2
Background
The cold slab on which everything rests.
ink
#e9e1d1#2b2733
Text, variables
Polished bone, scriptorium ink.
13.74:112.61:1AA
iron
#9a948c#64606c
Punctuation
The grilles that divide, the nails that hold.
5.94:15.30:1AA
incense
#9e8d99#8a7f8c
Comments
Smoke that rises and fades between the lines.
5.72:13.31:1exempt
crimson
#c42740#c42740
Accent, caret, selection
The heart of the reliquary, identical in both variants.
crimson_deep
#7e1023#7e1023
Selection border
Blood clotted at the edges of the selection.
blood
#e2586a#bb1f39
Keywords, control flow, booleans, tags, exceptions
The word that commands.
4.97:15.40:1AA
lapis
#6886bb#2d5c9c
Functions, JSON and YAML keys
The deep cold note, ground for missals.
4.86:15.82:1AA
tallow
#dccb9a#7e6410
Types, classes, namespaces, attributes
Candle wax for the names that endure.
11.10:14.89:1AA
amber
#cf9142#9f5c12
Numbers, constants
Ancient resin that traps the numbers.
6.62:14.53:1AA
sealwax
#c6784c#a24d20
Decorators, annotations, escapes, placeholders, regex
The seal pressed upon decorators, and on the escapes set inside strings.
5.27:15.02:1AA
malachite
#44a078#2f7a4e
Strings
Green stone for quoted words.
5.57:14.53:1AA
verdigris
#6bb3b0#1a706d
Properties, members
The cold patina that ages on church doors.
7.40:15.07:1AA
amethyst
#9c7bb0#7e3a9e
Builtins, self, YAML anchors
A bishop's gem for what is born with the language.
5.00:16.09:1AA
wine
#b27a90#8a3f64
Operators, imports, key-value colons
The chalice of operators.
5.17:16.11:1AA
petal
#c58f96#a8465f
Parameters
Dried petals pressed between the pages.
6.59:14.90:1AA
border#302f35#e9e1d1
gutter_fg#4e4a50#b3aa9aBeyond the editor
Eleven steps of stone and eleven of blood, interpolated in OKLCH, for building interfaces on top of Cremisi. Structure and identity, and nothing else, on purpose: syntax highlighting stays with the materials. Click a step to copy its hex.
Structure
Not a neutral grey. The hue slides from warm bone at 85° to the violet stone of the crypt at 285°, crossing a faintly rosy grey on the way: the family of incense. Ivory and Obsidian are the same stone, read from opposite ends.
#f4eee2
85°
= Ivory ground
Aa15.46
Aa1.00#e9e1d1
85°
= Obsidian ink, Ivory border
Aa13.74
Aa1.12#cfc6b8
79°
Aa10.57
Aa1.46#b3aa9a
82°
= Ivory gutter
Aa7.77
Aa1.99#9a948c
75°
= Obsidian iron
Aa5.94
Aa2.60#827a76
49°
Aa4.25
Aa3.64#696164
353°
Aa2.97
Aa5.20#4e4a50
315°
= Obsidian gutter
Aa2.06
Aa7.51#302f35
293°
= Obsidian border
Aa1.35
Aa11.48#17171c
286°
= Obsidian ground
Aa1.00
Aa15.46#0c0c10
285°
Aa1.09
Aa16.90Identity
Steps 400 to 800 are the reds the theme already speaks. Above them the scale opens toward the petal, for badges and alerts on Ivory; below, it thickens into clotted blood, for error and diff grounds on Obsidian.
#feeff0
13°
Aa16.01
Aa1.04#ffddde
16°
Aa14.16
Aa1.09#febec1
16°
Aa11.35
Aa1.36#fa969f
14°
Aa8.44
Aa1.83#ef6f80
14°
Aa6.17
Aa2.50#e2586a
16°
= blood
Aa4.97
Aa3.11#c42740
19°
= crimson
Aa3.16
Aa4.90#a11730
19°
Aa2.28
Aa6.78#7e1023
19°
= crimson deep
Aa1.69
Aa9.15#52151f
15°
Aa1.26
Aa12.23#301419
10°
Aa1.05
Aa14.66◆already a colour of the theme 4.5:1 or more: text 3:1: large text, borders, icons below 3:1: surfaces only
Chapels
NaveApseCryptCloisterChapels
NaveApseCryptCloisterstone-900
stone-50stone-950
stone-100stone-800
stone-200stone-700
stone-300stone-100
stone-900stone-400
stone-600blood-600
blood-600stone-50
stone-50blood-400
blood-700blood-500
blood-500blood-900
blood-100blood-200
blood-800blood-950
blood-50blood-800
blood-200blood-200
blood-800Twenty-two custom properties, --cremisi-stone-* and --cremisi-blood-*. The example above is built from nothing else: map them to roles in your own interface, as in the table, and Obsidian and Ivory follow.
/* Cremisi tonal scales · stone for structure, blood for identity */
:root {
--cremisi-stone-50: #f4eee2;
--cremisi-stone-100: #e9e1d1;
--cremisi-stone-200: #cfc6b8;
--cremisi-stone-300: #b3aa9a;
--cremisi-stone-400: #9a948c;
--cremisi-stone-500: #827a76;
--cremisi-stone-600: #696164;
--cremisi-stone-700: #4e4a50;
--cremisi-stone-800: #302f35;
--cremisi-stone-900: #17171c;
--cremisi-stone-950: #0c0c10;
--cremisi-blood-50: #feeff0;
--cremisi-blood-100: #ffddde;
--cremisi-blood-200: #febec1;
--cremisi-blood-300: #fa969f;
--cremisi-blood-400: #ef6f80;
--cremisi-blood-500: #e2586a;
--cremisi-blood-600: #c42740;
--cremisi-blood-700: #a11730;
--cremisi-blood-800: #7e1023;
--cremisi-blood-900: #52151f;
--cremisi-blood-950: #301419;
}
Preview
The theme at work in six languages. Italic for declarations, functions and types, bold for control flow, bold italic for booleans.
1import re
2from dataclasses import dataclass, field
3
4# The reliquary keeps what time cannot consume.
5SEAL = re.compile(r"^[A-Z]{3}-\d{4}$")
6MAX_RELICS = 0x2A
7
8
9@dataclass(frozen=True)
10class Relic:
11 name: str
12 century: int
13 materials: list[str] = field(default_factory=list)
14
15
16class Reliquary:
17 """A crypt casket, sealed with wax."""
18
19 def __init__(self, abbey: str, sealed: bool = True) -> None:
20 self.abbey = abbey
21 self.sealed = sealed
22 self._casket: dict[str, Relic] = {}
23
24 def enshrine(self, relic: Relic, *, code: str) -> bool:
25 if not SEAL.match(code):
26 raise ValueError(f"Broken seal: {code!r}\n")
27 for name in self._casket:
28 if name == relic.name:
29 return False
30 self._casket[relic.name] = relic
31 return len(self._casket) <= MAX_RELICS
32
33 @property
34 def antiquity(self) -> float | None:
35 centuries = [r.century for r in self._casket.values()]
36 return sum(centuries) / len(centuries) * 1.0 if centuries else None
1use std::collections::HashMap;
2use std::fmt;
3
4/// A votive candle: it burns while tallow remains.
5#[derive(Debug, Clone, PartialEq)]
6pub struct Candle {
7 pub altar: String,
8 tallow: f32,
9 lit: bool,
10}
11
12impl Candle {
13 pub const MAX_HOURS: f32 = 12.5;
14
15 pub fn new(altar: &str) -> Self {
16 Self { altar: altar.to_owned(), tallow: Self::MAX_HOURS, lit: false }
17 }
18
19 pub fn burn(&mut self, hours: f32) -> Result<f32, String> {
20 if !self.lit {
21 return Err(format!("The candle at the {} is out\n", self.altar));
22 }
23 self.tallow -= hours.min(self.tallow);
24 Ok(self.tallow)
25 }
26}
27
28impl fmt::Display for Candle {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "† {} [{:.1}h]", self.altar, self.tallow)
31 }
32}
33
34fn main() {
35 let mut chapel: HashMap<&str, Candle> = HashMap::new();
36 for (i, name) in ["Nave", "Apse", "Crypt"].iter().enumerate() {
37 let mut candle = Candle::new(name);
38 candle.lit = i % 2 == 0;
39 chapel.insert(name, candle);
40 }
41 match chapel.get_mut("Crypt").map(|c| c.burn(3.0)) {
42 Some(Ok(left)) => println!("Tallow left for {left} hours"),
43 Some(Err(e)) => eprintln!("{e}"),
44 None => unreachable!(),
45 }
46}
1#include <stdio.h>
2#include <stdbool.h>
3#include <string.h>
4
5#define MAX_NICHES 0x40
6#define SEALED (1u << 3)
7
8/* Each niche of the ossuary keeps one name and one seal. */
9typedef enum { BONE, ASH, DUST } relic_kind;
10
11typedef struct {
12 char name[32];
13 relic_kind kind;
14 unsigned flags;
15 int century;
16} niche;
17
18static niche ossuary[MAX_NICHES];
19static size_t filled = 0;
20
21int inter(const char *name, relic_kind kind, int century) {
22 if (filled >= MAX_NICHES || name == NULL) {
23 fprintf(stderr, "The ossuary is full\n");
24 return -1;
25 }
26 niche *n = &ossuary[filled++];
27 strncpy(n->name, name, sizeof n->name - 1);
28 n->kind = kind;
29 n->century = century;
30 n->flags |= SEALED;
31 return (int)filled;
32}
33
34int main(void) {
35 const char *saints[] = { "Agatha", "Lucia", "Cecilia" };
36 for (int i = 0; i < 3; i++) {
37 inter(saints[i], i % 2 ? ASH : BONE, 3 + i);
38 }
39 bool sealed = ossuary[0].flags & SEALED;
40 printf("%zu niches, first sealed: %s\t%.1f%%\n",
41 filled, sealed ? "true" : "false", 100.0 * filled / MAX_NICHES);
42 return 0;
43}
1import { readFile } from "node:fs/promises";
2import type { Manuscript } from "./codices";
3
4// The scribes work by candlelight, one page at a time.
5const ILLUMINATIONS = /\b(initial|border)\b/gi;
6export const PAGES_PER_NIGHT = 24;
7
8interface Scribe {
9 readonly name: string;
10 order: "benedictine" | "cistercian";
11 awake?: boolean;
12}
13
14export class Scriptorium<T extends Manuscript> {
15 private codices = new Map<string, T>();
16
17 constructor(private readonly prior: Scribe) {}
18
19 async transcribe(path: string, scribe: Scribe): Promise<number> {
20 const text = await readFile(path, "utf-8");
21 let illuminations = 0;
22 for (const line of text.split("\n")) {
23 if (line.trim() === "") continue;
24 illuminations += line.match(ILLUMINATIONS)?.length ?? 0;
25 }
26 if (scribe.awake === false) {
27 throw new Error(`${scribe.name} fell asleep over the codex`);
28 }
29 return Math.min(illuminations, PAGES_PER_NIGHT);
30 }
31}
1%YAML 1.2
2---
3# Liturgical calendar of the crypt
4abbey: St. Mary of Cremisi
5founded: 1137
6sealed: true
7prior: null
8canonical_hours: &hours
9 - matins
10 - lauds
11 - vespers
12 - compline
13crypt:
14 temperature: 11.5
15 candles_lit: 7
16 hours: *hours
17 inscription: |
18 Here rests what was kept,
19 beneath cold stone and red wax.
20 motto: "Ex umbra in lucem"
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Codex Cremisi</title>
6 <link rel="stylesheet" href="/css/syntax.css">
7 </head>
8 <body data-theme="obsidian">
9 <!-- The initial is illuminated by hand -->
10 <article class="codex" id="folio-42">
11 <h1>Liber <em>Sanguinis</em></h1>
12 <p>Written by <span class="tallow">candlelight</span> & incense.</p>
13 <button type="button" disabled>Break the seal</button>
14 </article>
15 </body>
16</html>
Typography of code
Each font style carries a meaning. The shape of the letter tells you the role before the colour does.
Legibility
Every code token clears the WCAG threshold of 4.5:1 against its own variant's background. Only comments, the smoke of incense, may fall below.
#17171c
Ivory #f4eee2Rite of entry
Sublime Text and VS Code are below. Further ports are available in the repository: vari-sh/Cremisi-Theme.
Packages/User folder (Preferences → Browse Packages…).UI: Select Color Scheme.Ctrl+P and run ext install vari-sh.cremisi-theme.Extensions: Install from VSIX….Preferences: Color Theme and pick Cremisi Obsidian or Cremisi Ivory. To follow the system appearance:{
"window.autoDetectColorScheme": true,
"workbench.preferredDarkColorTheme": "Cremisi Obsidian",
"workbench.preferredLightColorTheme": "Cremisi Ivory"
}The codex of decisions
Crimson #c42740 is the only colour shared by both variants. It is the accent.
Rather than black, the editor background is slate #17171c: the cold stone of a crypt.
Every hue runs warm except lapis and verdigris, the only cold notes: functions and keys, then properties.
Malachite and tallow carry strings and types; amethyst marks builtins and self, the words the language owns.
Incense lives in comments, a soft smoke that recedes behind the code.
Sealing wax marks decorators outside strings, escapes and regex inside them: the two never meet.
Interfaces get only two tonal scales, stone and blood: structure and identity. Syntax highlighting stays with the materials.