This repo is the single source of truth for the Dracula-Nineties HTML conventions. It holds the
stylesheet, the palette, the Mermaid init script, and the sample fixtures. Consumers pull it in
through a pinned git submodule at external/dracula-nineties/. One repo holds the payload, so no
copy can drift from it.
The palette is a 1990s register of Dracula: a black ground, a silver structural tier, and Dracula’s own accents kept as the accent colors. The objective is 1990s web design: the dense single-page document, the small type, the hairline rules, and the boxy, table-heavy layouts that period used, restated as one inline stylesheet with no build step. The document shell carries that register too: a filled title box, a narrow left link rail, a center stack of bordered panels, and a bracketed link row, after the Be Inc. home page of 1996. This repo is not a fork of Dracula. Light mode is the silver register of the same idea.
Three files answer three different questions:
| file | question | audience |
|---|---|---|
| CONTRACT.md | What must my generator emit? | a consumer’s generator, or the agent that writes it |
| NOTES.md | Why is this declaration the way it is, and what must I not change? | anyone who edits the payload |
| README.md | What is this, and how do I install it? | a person who arrives for the first time |
This file repeats neither one. It orients the reader and it links out.
GitHub Pages renders these four from main. There is no build step.
Those four follow your system appearance. A dark-mode reader therefore never sees the light palette. Four more pages force it: light.html, light-conn-map.html and light-timeline.html and light-charts.html.
Do not inline CSS from a light preview. Its stylesheet carries rewritten @media conditions,
so it is locked to light and it is not the payload. Each light page says so in a banner. High
contrast has no preview page. CI renders it and attaches the image to each pull request. See
Appearance modes.
| File | What it is |
|---|---|
dracula-nineties.css |
The stylesheet payload (template v2.8.0, oklch palette). The complete <style>…</style> block, with its wrapper tags and its leading indent. Consumers inline it verbatim into every generated file. |
mermaid.js |
The Mermaid init script, with its <script type="module"> wrapper. It holds the pinned CDN import, the init call, and the zoom overlay. Inline it only when the page has a mermaid fence. Bump the CDN pin here. |
filter.js |
The filter-box script, with its wrapper. It wires each input.filter-box to the siblings that follow it. Inline it only when the page has a filter box. CONTRACT.md § 6 states the scope. |
mermaid-palette.json |
Mermaid’s hex palette for each themeVariables key, in dark and light, plus the classDef node roles. Mermaid cannot read oklch() or var(). Each entry names its :root source, and CI recomputes every hex. |
tokens.css |
Palette reference. Generated from the :root block. Do not edit it by hand. |
scripts/build-sample.nu |
The regenerator. It rebuilds tokens.css and all eight fixtures. Run it after any payload change. |
samples/dark.html |
Living style fixture, and the executable specification. Generated. Do not edit it by hand. |
samples/dark-conn-map.html |
Conn-map fixture. It uses <body class="conn-map"> with the sections in Links-then-Graph order. Generated. |
samples/dark-timeline.html |
Timeline fixture, and the only one built from real content. Generated. |
samples/dark-charts.html |
Chart fixture: table.bar-chart and a Mermaid pie showData fence for the same four numbers, plus the guidance on which one to draw, and twelve more Mermaid fence types verified against this palette. Generated. |
CONTRACT.md |
The consumer checklist. It stays imperative and short, because a consumer’s agent reads it on every bump. |
The four light previews are deliberately not contract files. The rest of the repo splits by
who runs a file. A person types the scripts/*.nu commands. CI runs the .github/*.py helpers.
Both kinds resolve every path from the repo root. See Repo layout.
The current release is v2.8.0. Consumers reach it through a git submodule. To refresh it,
run git submodule update --remote external/dracula-nineties and then commit the pointer.
Read CONTRACT.md before you wire a generator. It states five things:
Most of § 2 is modelled in samples/dark.html, two requirements only in
samples/dark-timeline.html, one only in samples/dark-charts.html, and three in no fixture yet,
which each of those three says. CI fails
when a fixture drifts from the stylesheet, so when CONTRACT.md and a fixture disagree about
markup, the fixture is right. The single exception is flagged in § 2 itself: the fixture’s
quadrantChart breaks its own requirement on purpose, as a stress case.
Four things are worth knowing before you get there:
sed '1d;$d'. The
wrapper is exactly one line at each end, and CI holds it there.@layer dracula-nineties.
Unlayered author styles beat layered ones, so a plain h1 { color: … } overrides the template.
Load your CSS in any order. The !important declarations go the other way. To beat one of
those, declare your own layer ahead of dracula-nineties. See
Cascade layer.prefers-contrast: more raises every accent. prefers-color-scheme: light swaps in a full light
palette. Mermaid diagrams follow the light palette, because mermaid.js reads a CSS token at
init. A reader who changes system appearance with the page open sees a stale diagram until the
next reload. See Appearance modes.themes/ projects the same :root palette into seven editors and terminals, plus Slack. The
editor and terminal files are not part of the consumer contract, because no submodule reads
them. The repo generates and gates them from a .in template beside each output, except iTerm2’s
plist, which scripts/create-themes.nu builds straight from the palette because a plist color is
three float components, not a hex string. The Slack entry has no file format to generate: it is a
hand-copied color string, documented in themes/slack/README.md.
Rider, Zed, Ghostty, iTerm2 and VS Code ship both a dark and a light variant, projected
from the same :root palette and its prefers-color-scheme: light override respectively.
opencode, tmux and Slack ship dark only: opencode’s and tmux’s theme formats have no
appearance-switch mechanism to project a light variant into, and Slack is a hand-copied string
with nowhere to hold a second one.
| Theme | Files | Modes | Install |
|---|---|---|---|
| Rider | dracula-nineties.theme.json/.icls (dark), dracula-nineties-light.theme.json/.icls (light) |
dark + light | Settings, Plugins, gear, Install Plugin from Disk, themes/rider/dist/dracula-nineties-rider-<version>.zip, pick either theme from the theme list |
| Zed | dracula-nineties.json (one file, both appearances) |
dark + light | Copy to ~/.config/zed/themes/ |
| Ghostty | dracula-nineties, dracula-nineties-light |
dark + light | Copy both to ~/.config/ghostty/themes/, then set theme = dark:dracula-nineties,light:dracula-nineties-light |
| iTerm2 | dracula-nineties.itermcolors, dracula-nineties-light.itermcolors |
dark + light | Preferences, Profiles, Colors, Color Presets, Import both; check “Use different colors for light and dark mode” and assign each preset to its appearance |
| opencode | dracula-nineties.json |
dark only | Copy to ~/.config/opencode/themes/, then set "theme": "dracula-nineties" in opencode.json |
| VS Code | package.json plus themes/dracula-nineties-color-theme.json (dark) and themes/dracula-nineties-light-color-theme.json (light) |
dark + light | Extensions view, ... menu, Install from Location, pick themes/vscode/, then select either theme |
| tmux | dracula-nineties.conf |
dark only | source-file it from ~/.tmux.conf |
| Slack | themes/slack/README.md (color string, no file to install) |
dark only | Preferences, Themes, Custom Theme, paste string |
nu scripts/create-themes.nu # write every theme, then package the Rider plugin
nu scripts/create-themes.nu --check # fail if any output drifts from its template
Rider loads a UI theme only from a plugin. That is why there is an artifact to build. It ships
as a zip that wraps a jar, because Install Plugin from Disk refuses a bare jar. Delete any old
dracula-nineties-rider-*.jar from your plugins directory before you install the zip.
nu scripts/maintain.nu release attaches the plugin and a themes zip to the GitHub release.
The editor slot map is deliberately not the prose slot map. Do not answer “the theme looks
washed out” by a chroma raise in :root. Editor themes holds the
reasoning. themes/rider/README.md holds the whole mapping.
dracula-nineties.css :root block and nowhere else. When
Mermaid needs that color, recompute its hex in mermaid-palette.json and mermaid.js.nu scripts/build-sample.nu to regenerate tokens.css and the fixtures.nu scripts/maintain.nu bump <version> to stamp the stylesheet and this README. Write the
version as vX.Y.Z in prose. A bare X.Y.Z goes stale with nobody to notice.main, with a conventional message.gh pr create --fill && gh pr checks --watch, then gh pr merge --squash. The merge is
what the required check gates.main, run nu scripts/maintain.nu release <version>. It refuses a HEAD that
is not origin/main. It refuses a version the stylesheet does not carry. It waits for the
checks named in REQUIRED_CHECKS on that exact SHA. It writes an annotated tag only when every
one of those concludes success. Every other check run prints as (advisory).git submodule update --remote external/dracula-nineties in each consumer repo.Only a pull request can satisfy a required status check. The check runs after a push, so
git push origin main can never satisfy it. GitHub takes the push and records Bypassed rule
violations. Consumers pin to tags, so a tag on an ungated commit hands every one of them an
unverified payload. When a push reports a bypass, say so and revert. Do not tag on top of it.
AGENTS.md states this flow as plain shell, and states the rules that have no
exception. It is the only instruction file in this repo and the one any agent reads, whichever
harness runs it, Claude Code included. Its Harness entry points section lists the three Claude Code
skills, release, design-audit and inspiration-analyze, each of which packages a flow stated in full there.
.github/workflows/contract-check.yml runs on every push and pull request.
nu scripts/maintain.nu check mirrors it step for step, so a local pass and a CI pass mean the
same thing. Together they verify:
<style> and <script> wrappers.theme: 'base' in mermaid.js.mermaid-palette.json.nu scripts/maintain.nu selftest.One check runs the payload instead of reading it. .github/script-probe.py loads
samples/dark.html in headless Chrome, drives filter.js and mermaid.js, and asserts what a
reader would see. Everything else counts blocks, compares bytes or measures colors, and none of
that asks whether a handler attaches to anything: the fixture shipped an inert input.filter-box
for six releases with every other check green. It also renders the page with forced-colors
switched on and asserts that block’s structure, which is the only coverage that mode has, because
its correctness is not a color.
One palette feeds several projections. The :root block is the only source of color truth.
.github/palette-check.py runs eleven checks over it:
classdef fills, dark and light, and the letter each set paints on its own fill./* was */ provenance comments.mermaid.js.prefers-contrast: more, 4.5 in print.--mermaid-scheme in both directions, and the scroll breakpoint pinned across two files.--surface is the text.--highlight token.pieOpacity.table.bar-chart band, and the two pins that keep a CSS chart
visible in print and out of the way in forced colors.A measurement in prose is not a gate. That is why those checks exist, and why NOTES.md carries the decisions rather than the numbers.
CI renders every appearance mode instead of only parsing it. .github/render-modes.py opens
each fixture in each mode. It then asserts that the page paints that mode’s --surface. The images
upload as a pull-request artifact and stay advisory. The assertions are the gate.
If you find a violation in a consumer, fix that consumer to read from external/dracula-nineties/. Do
not add a copy.
MIT, see LICENSE.