dracula-nineties

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.

Live previews

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.

The eleven files

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.

Consumers

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:

  1. What to inline (§ 1).
  2. The twenty-seven markup requirements a generator owes (§ 2), each pointing at a string to search for in a fixture rather than at a line number.
  3. What changed in each release (§ 3).
  4. How to detect a stale artifact (§ 4).
  5. What each pin mode costs (§ 5).

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:

Editor themes

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.

Releases

  1. Edit the payload. Colors change in the dracula-nineties.css :root block and nowhere else. When Mermaid needs that color, recompute its hex in mermaid-palette.json and mermaid.js.
  2. Run nu scripts/build-sample.nu to regenerate tokens.css and the fixtures.
  3. Run 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.
  4. Commit to a branch, never straight to main, with a conventional message.
  5. Run gh pr create --fill && gh pr checks --watch, then gh pr merge --squash. The merge is what the required check gates.
  6. From an updated 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).
  7. Run 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.

Contract enforcement

.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:

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:

  1. Hex projections in both Mermaid palettes.
  2. The classdef fills, dark and light, and the letter each set paints on its own fill.
  3. The /* was */ provenance comments.
  4. Stray hex in mermaid.js.
  5. Contrast floors in all four modes. The floors follow the palette: 4.0 in dark, 3.2 in light, 7.0 in prefers-contrast: more, 4.5 in print.
  6. --mermaid-scheme in both directions, and the scroll breakpoint pinned across two files.
  7. The sRGB gamut ceiling.
  8. The inverted pairs, where an accent is the ground and --surface is the text.
  9. The relative-color --highlight token.
  10. The pie slice label against every slice fill, and pieOpacity.
  11. The number that sits on a 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.

License

MIT, see LICENSE.