feat(zed): add Australis Dark theme for Zed

Adds a Zed theme generated from the canonical palette in the root
README, alongside the existing wezterm and zellij configurations.

The 16 terminal colors carry over unchanged from wezterm/config.lua.
Syntax highlighting follows the spec's stated preference order (blue,
cyan, green) with the Dawn accents reserved for semantic signals and
the rare token roles.

Zed addresses UI surfaces the 16-color terminal palette does not
define. Two neutrals are derived for chrome (#2a2e39, L19) and
elevated surfaces (#313440, L22), both interpolated in CIELAB along
the palette's own neutral ramp so they sit on the ramp the spec
defines. ANSI dim variants are mixed 45% toward the background in LAB
to preserve hue. Overlays use an alpha channel over palette colors
rather than new opaque values.

The theme is generated by build-theme.py, which validates output
against the live Zed theme schema (v0.2.0) and fails if any of its
142 style keys is missing.
This commit is contained in:
2026-07-27 21:35:09 -07:00
parent f7208e5390
commit 0748522245
3 changed files with 945 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
## Zed Australis Dark
### Installation Instructions
Copy `australis-dark.json` into your Zed themes directory:
```sh
mkdir -p ~/.config/zed/themes
cp australis-dark.json ~/.config/zed/themes/
```
Zed loads themes from that directory without a restart. Select
**Australis Dark** from `theme selector: toggle` (`cmd-k cmd-t`), or set it
in `settings.json`:
```json
"theme": {
"mode": "dark",
"dark": "Australis Dark"
}
```
### Palette usage
The 16 terminal colors are carried over unchanged from
[`wezterm/config.lua`](../wezterm/config.lua). Syntax highlighting follows the
spec's stated preference order — blue, then cyan, then green — with the Dawn
accents kept rare:
| Role | Color |
| :-------------------------------------- | :------------------------------- |
| Keywords, tags | Blue `#6388d8` |
| Functions, methods | Bright cyan `#42dcd1` |
| Types, constructors, enums | Cyan `#00b1a8` |
| Strings | Green `#16b866` |
| String escapes, regex | Bright green `#51e08a` |
| Numbers, booleans, constants | Bright blue `#a4c4ff` |
| Comments | Dark 50 `#6e7882` |
| Operators, punctuation | Dark 60 `#86929d` |
| Decorators, `self`, preprocessor | Magenta `#9d78ff` / `#d8adff` |
Dawn accents otherwise appear only where the editor demands a semantic
signal: red for errors and deletions, yellow for warnings and modifications,
bright yellow for search matches (kept distinct from the blue selection and
the cyan bracket match).
### Derived colors
Zed addresses UI surfaces the 16-color terminal palette does not define. Two
neutrals are therefore derived, both interpolated in CIELAB **along the
palette's own neutral ramp** so they land on the ramp the spec defines rather
than introducing a foreign hue:
| Derived | Hex | LAB L\* | Ramp position | Used for |
| :------- | :-------- | :------ | :--------------- | :------------------------------ |
| Chrome | `#2a2e39` | 19 | 40% of L15 → L25 | Tab bar, panels, status bar |
| Elevated | `#313440` | 22 | 70% of L15 → L25 | Popovers, modals, context menus |
The editor canvas itself remains the palette's black `#222531`, with the
active tab fusing into it. ANSI `dim_*` variants, which Zed requires and the
palette does not specify, are each mixed 45% toward the background in LAB so
hue is preserved.
Overlay colors (selections, hover states, status backgrounds) use an alpha
channel over the palette color rather than a new opaque value, keeping the
underlying color recognizable.
### Regenerating the theme
`australis-dark.json` is generated, not hand-maintained. Edit
`build-theme.py` and re-run it:
```sh
python3 build-theme.py australis-dark.json
```
The script requires no third-party packages. It fetches the live Zed theme
schema and fails if any of the 142 style keys it defines is missing, or if a
key outside the schema is emitted; the check is skipped when offline.
+426
View File
@@ -0,0 +1,426 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Australis",
"author": "Vuong Hoang",
"themes": [
{
"name": "Australis Dark",
"appearance": "dark",
"style": {
"background.appearance": "opaque",
"background": "#2a2e39",
"surface.background": "#2a2e39",
"elevated_surface.background": "#313440",
"panel.background": "#2a2e39",
"status_bar.background": "#2a2e39",
"title_bar.background": "#2a2e39",
"title_bar.inactive_background": "#2a2e39",
"toolbar.background": "#222531",
"tab_bar.background": "#2a2e39",
"tab.active_background": "#222531",
"tab.inactive_background": "#2a2e39",
"drop_target.background": "#6388d833",
"border": "#414751",
"border.variant": "#373b46",
"border.focused": "#6388d8",
"border.selected": "#6388d8",
"border.transparent": "#00000000",
"border.disabled": "#373b46",
"pane.focused_border": "#6388d8",
"pane_group.border": "#373b46",
"panel.focused_border": "#6388d8",
"panel.indent_guide": "#373b46",
"panel.indent_guide_active": "#565f69",
"panel.indent_guide_hover": "#414751",
"element.background": "#2a2e39",
"element.hover": "#373b4699",
"element.active": "#414751",
"element.selected": "#6388d833",
"element.disabled": "#373b4666",
"ghost_element.background": "#00000000",
"ghost_element.hover": "#373b4680",
"ghost_element.active": "#41475199",
"ghost_element.selected": "#6388d826",
"ghost_element.disabled": "#00000000",
"text": "#a9bcc3",
"text.muted": "#86929d",
"text.placeholder": "#6e7882",
"text.disabled": "#565f69",
"text.accent": "#a4c4ff",
"link_text.hover": "#a4c4ff",
"icon": "#a9bcc3",
"icon.muted": "#86929d",
"icon.disabled": "#565f69",
"icon.placeholder": "#6e7882",
"icon.accent": "#42dcd1",
"editor.background": "#222531",
"editor.foreground": "#a9bcc3",
"editor.gutter.background": "#222531",
"editor.subheader.background": "#2a2e39",
"editor.active_line.background": "#373b4659",
"editor.highlighted_line.background": "#373b4680",
"editor.line_number": "#565f69",
"editor.active_line_number": "#cce7ec",
"editor.invisible": "#565f69",
"editor.wrap_guide": "#373b4680",
"editor.active_wrap_guide": "#565f69",
"editor.indent_guide": "#373b46",
"editor.indent_guide_active": "#565f69",
"editor.document_highlight.read_background": "#6388d826",
"editor.document_highlight.write_background": "#42dcd133",
"editor.document_highlight.bracket_background": "#6388d838",
"search.match_background": "#ffe14e38",
"scrollbar.thumb.background": "#565f6966",
"scrollbar.thumb.hover_background": "#6e788299",
"scrollbar.thumb.border": "#00000000",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#00000000",
"error": "#ff491a",
"error.background": "#ff491a1f",
"error.border": "#ff491a66",
"warning": "#e1c631",
"warning.background": "#e1c6311f",
"warning.border": "#e1c63166",
"info": "#6388d8",
"info.background": "#6388d81f",
"info.border": "#6388d866",
"success": "#16b866",
"success.background": "#16b8661f",
"success.border": "#16b86666",
"hint": "#00b1a8",
"hint.background": "#00b1a81f",
"hint.border": "#00b1a866",
"predictive": "#565f69",
"predictive.background": "#565f691f",
"predictive.border": "#565f6966",
"created": "#16b866",
"created.background": "#16b8661f",
"created.border": "#16b86666",
"modified": "#e1c631",
"modified.background": "#e1c6311f",
"modified.border": "#e1c63166",
"deleted": "#ff491a",
"deleted.background": "#ff491a1f",
"deleted.border": "#ff491a66",
"renamed": "#6388d8",
"renamed.background": "#6388d81f",
"renamed.border": "#6388d866",
"conflict": "#ff854f",
"conflict.background": "#ff854f1f",
"conflict.border": "#ff854f66",
"ignored": "#6e7882",
"ignored.background": "#6e78821f",
"ignored.border": "#6e788266",
"hidden": "#6e7882",
"hidden.background": "#6e78821f",
"hidden.border": "#6e788266",
"unreachable": "#6e7882",
"unreachable.background": "#6e78821f",
"unreachable.border": "#6e788266",
"terminal.background": "#222531",
"terminal.foreground": "#a9bcc3",
"terminal.bright_foreground": "#cce7ec",
"terminal.dim_foreground": "#6e7882",
"terminal.ansi.background": "#222531",
"terminal.ansi.black": "#222531",
"terminal.ansi.red": "#ff491a",
"terminal.ansi.green": "#16b866",
"terminal.ansi.yellow": "#e1c631",
"terminal.ansi.blue": "#6388d8",
"terminal.ansi.magenta": "#9d78ff",
"terminal.ansi.cyan": "#00b1a8",
"terminal.ansi.white": "#a9bcc3",
"terminal.ansi.bright_black": "#373b46",
"terminal.ansi.bright_red": "#ff854f",
"terminal.ansi.bright_green": "#51e08a",
"terminal.ansi.bright_yellow": "#ffe14e",
"terminal.ansi.bright_blue": "#a4c4ff",
"terminal.ansi.bright_magenta": "#d8adff",
"terminal.ansi.bright_cyan": "#42dcd1",
"terminal.ansi.bright_white": "#cce7ec",
"terminal.ansi.dim_black": "#2c303b",
"terminal.ansi.dim_red": "#9a3e2b",
"terminal.ansi.dim_green": "#2e724f",
"terminal.ansi.dim_yellow": "#88783a",
"terminal.ansi.dim_blue": "#465988",
"terminal.ansi.dim_magenta": "#63519c",
"terminal.ansi.dim_cyan": "#2a6e70",
"terminal.ansi.dim_white": "#6e7882",
"accents": [
"#6388d8",
"#00b1a8",
"#16b866",
"#9d78ff",
"#e1c631",
"#42dcd1",
"#a4c4ff",
"#51e08a"
],
"players": [
{
"cursor": "#6388d8",
"background": "#6388d8",
"selection": "#6388d83d"
},
{
"cursor": "#16b866",
"background": "#16b866",
"selection": "#16b8663d"
},
{
"cursor": "#9d78ff",
"background": "#9d78ff",
"selection": "#9d78ff3d"
},
{
"cursor": "#00b1a8",
"background": "#00b1a8",
"selection": "#00b1a83d"
},
{
"cursor": "#ff854f",
"background": "#ff854f",
"selection": "#ff854f3d"
},
{
"cursor": "#e1c631",
"background": "#e1c631",
"selection": "#e1c6313d"
},
{
"cursor": "#42dcd1",
"background": "#42dcd1",
"selection": "#42dcd13d"
},
{
"cursor": "#d8adff",
"background": "#d8adff",
"selection": "#d8adff3d"
}
],
"syntax": {
"comment": {
"color": "#6e7882",
"font_style": "italic",
"font_weight": null
},
"comment.doc": {
"color": "#86929d",
"font_style": "italic",
"font_weight": null
},
"operator": {
"color": "#86929d",
"font_style": null,
"font_weight": null
},
"punctuation": {
"color": "#86929d",
"font_style": null,
"font_weight": null
},
"punctuation.bracket": {
"color": "#86929d",
"font_style": null,
"font_weight": null
},
"punctuation.delimiter": {
"color": "#86929d",
"font_style": null,
"font_weight": null
},
"punctuation.list_marker": {
"color": "#6388d8",
"font_style": null,
"font_weight": null
},
"punctuation.special": {
"color": "#d8adff",
"font_style": null,
"font_weight": null
},
"variable": {
"color": "#a9bcc3",
"font_style": null,
"font_weight": null
},
"property": {
"color": "#9daeb6",
"font_style": null,
"font_weight": null
},
"primary": {
"color": "#a9bcc3",
"font_style": null,
"font_weight": null
},
"hint": {
"color": "#6e7882",
"font_style": "italic",
"font_weight": null
},
"predictive": {
"color": "#565f69",
"font_style": "italic",
"font_weight": null
},
"keyword": {
"color": "#6388d8",
"font_style": null,
"font_weight": null
},
"tag": {
"color": "#6388d8",
"font_style": null,
"font_weight": null
},
"link_text": {
"color": "#6388d8",
"font_style": "italic",
"font_weight": null
},
"label": {
"color": "#a4c4ff",
"font_style": null,
"font_weight": null
},
"title": {
"color": "#a4c4ff",
"font_style": null,
"font_weight": 700
},
"function": {
"color": "#42dcd1",
"font_style": null,
"font_weight": null
},
"function.definition": {
"color": "#42dcd1",
"font_style": null,
"font_weight": null
},
"function.method": {
"color": "#42dcd1",
"font_style": null,
"font_weight": null
},
"function.special.definition": {
"color": "#42dcd1",
"font_style": null,
"font_weight": null
},
"type": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"type.interface": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"type.super": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"constructor": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"enum": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"variant": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"link_uri": {
"color": "#00b1a8",
"font_style": null,
"font_weight": null
},
"emphasis": {
"color": "#42dcd1",
"font_style": "italic",
"font_weight": null
},
"emphasis.strong": {
"color": "#42dcd1",
"font_style": null,
"font_weight": 700
},
"string": {
"color": "#16b866",
"font_style": null,
"font_weight": null
},
"string.escape": {
"color": "#51e08a",
"font_style": null,
"font_weight": null
},
"string.regex": {
"color": "#51e08a",
"font_style": null,
"font_weight": null
},
"string.special": {
"color": "#51e08a",
"font_style": null,
"font_weight": null
},
"string.special.symbol": {
"color": "#51e08a",
"font_style": null,
"font_weight": null
},
"text.literal": {
"color": "#16b866",
"font_style": null,
"font_weight": null
},
"number": {
"color": "#a4c4ff",
"font_style": null,
"font_weight": null
},
"boolean": {
"color": "#a4c4ff",
"font_style": null,
"font_weight": null
},
"constant": {
"color": "#a4c4ff",
"font_style": null,
"font_weight": null
},
"attribute": {
"color": "#d8adff",
"font_style": null,
"font_weight": null
},
"variable.special": {
"color": "#9d78ff",
"font_style": "italic",
"font_weight": null
},
"preproc": {
"color": "#9d78ff",
"font_style": null,
"font_weight": null
},
"embedded": {
"color": "#9d78ff",
"font_style": null,
"font_weight": null
}
}
}
}
]
}
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env python3
"""Generate the Australis Dark theme for Zed from the canonical palette.
Palette source: ../README.md ("All Colors and Hex Codes")
Target schema: https://zed.dev/schema/themes/v0.2.0.json
Usage:
python3 build-theme.py [output.json] # defaults to australis-dark.json
Two classes of color appear in the output:
1. PALETTE - verbatim from the repo README. Never altered.
2. DERIVED - computed from the palette, and only where Zed requires a step
the 16-color terminal palette does not define (UI elevation
surfaces, dim ANSI variants). Every derived color is
interpolated in CIELAB *along the palette's own neutral ramp*,
so it lands on the ramp the spec defines rather than
introducing a foreign hue. Overlays use an alpha channel so
the underlying palette color stays recognizable.
The script self-checks against the live Zed schema when the network is
available: every style key the schema defines must be present, and no key
outside the schema may be emitted.
"""
import json
import sys
# ---------------------------------------------------------------------------
# CIELAB (D65) conversion - used only to derive on-ramp neutrals
# ---------------------------------------------------------------------------
WHITEPOINT = (0.95047, 1.0, 1.08883)
def _hex_to_rgb(h):
h = h.lstrip("#")
return tuple(int(h[i:i + 2], 16) / 255 for i in (0, 2, 4))
def _rgb_to_hex(rgb):
return "#" + "".join("%02x" % max(0, min(255, round(c * 255))) for c in rgb)
def _linearize(c):
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
def _delinearize(c):
return c * 12.92 if c <= 0.0031308 else 1.055 * (c ** (1 / 2.4)) - 0.055
def _f(t):
return t ** (1 / 3) if t > 216 / 24389 else (841 / 108) * t + 4 / 29
def _f_inv(t):
return t ** 3 if t ** 3 > 216 / 24389 else (t - 4 / 29) * 108 / 841
def hex_to_lab(h):
r, g, b = (_linearize(c) for c in _hex_to_rgb(h))
xyz = (0.4124564 * r + 0.3575761 * g + 0.1804375 * b,
0.2126729 * r + 0.7151522 * g + 0.0721750 * b,
0.0193339 * r + 0.1191920 * g + 0.9503041 * b)
fx, fy, fz = (_f(v / w) for v, w in zip(xyz, WHITEPOINT))
return (116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz))
def lab_to_hex(lab):
L, a_, b_ = lab
fy = (L + 16) / 116
x, y, z = (_f_inv(fy + a_ / 500) * WHITEPOINT[0],
_f_inv(fy) * WHITEPOINT[1],
_f_inv(fy - b_ / 200) * WHITEPOINT[2])
return _rgb_to_hex(tuple(_delinearize(v) for v in (
3.2404542 * x - 1.5371385 * y - 0.4985314 * z,
-0.9692660 * x + 1.8760108 * y + 0.0415560 * z,
0.0556434 * x - 0.2040259 * y + 1.0572252 * z)))
def lab_mix(color_a, color_b, t):
"""Interpolate in CIELAB. t=0 -> color_a, t=1 -> color_b."""
la, lb = hex_to_lab(color_a), hex_to_lab(color_b)
return lab_to_hex(tuple(x + (y - x) * t for x, y in zip(la, lb)))
# ---------------------------------------------------------------------------
# PALETTE - verbatim from the repo README
# ---------------------------------------------------------------------------
# Ice / Sea - the neutral ramp, indexed by CIELAB lightness
L15 = "#222531" # black - background
L25 = "#373b46" # bright black
L30 = "#414751" # dark 30
L40 = "#565f69" # dark 40
L50 = "#6e7882" # dark 50
L60 = "#86929d" # dark 60
L70 = "#9daeb6" # bright 70
L75 = "#a9bcc3" # white - foreground
L80 = "#b3cbcf" # bright 80
L90 = "#cce7ec" # bright white - highlight
# Aurora - primary colors (used generously; preference order blue > cyan > green)
BLUE = "#6388d8"
BLUE_BR = "#a4c4ff"
CYAN = "#00b1a8"
CYAN_BR = "#42dcd1"
GREEN = "#16b866"
GREEN_BR = "#51e08a"
# Dawn - accent colors (used sparingly)
RED = "#ff491a"
RED_BR = "#ff854f"
YELLOW = "#e1c631"
YELLOW_BR = "#ffe14e"
MAGENTA = "#9d78ff"
MAGENTA_BR = "#d8adff"
# ---------------------------------------------------------------------------
# DERIVED - sampled from the palette's own neutral ramp
# ---------------------------------------------------------------------------
# The palette's darkest step (L15) is the editor canvas. Zed additionally needs
# chrome and floating-surface elevations between L15 and L25; sampling the
# L15 -> L25 segment keeps both on-ramp.
CHROME = lab_mix(L15, L25, 0.40) # ~L19 - tab bar, panels, status bar
ELEVATED = lab_mix(L15, L25, 0.70) # ~L22 - popovers, modals, context menus
TRANSPARENT = "#00000000"
def dim(color):
"""ANSI dim variant: 45% toward the background in LAB, preserving hue."""
return lab_mix(color, L15, 0.45)
def alpha(color, value):
"""Attach an 8-bit alpha channel to a palette color (Zed takes #RRGGBBAA)."""
return "%s%02x" % (color, value)
# ---------------------------------------------------------------------------
# Syntax
# ---------------------------------------------------------------------------
def hl(color, style=None, weight=None):
return {"color": color, "font_style": style, "font_weight": weight}
# Aurora carries the bulk of the syntax volume; Dawn accents stay rare -
# only variable.special, decorators and preprocessor tokens.
SYNTAX = {
# Neutrals - structure that should recede
"comment": hl(L50, style="italic"),
"comment.doc": hl(L60, style="italic"),
"operator": hl(L60),
"punctuation": hl(L60),
"punctuation.bracket": hl(L60),
"punctuation.delimiter": hl(L60),
"punctuation.list_marker": hl(BLUE),
"punctuation.special": hl(MAGENTA_BR),
"variable": hl(L75),
"property": hl(L70),
"primary": hl(L75),
"hint": hl(L50, style="italic"),
"predictive": hl(L40, style="italic"),
# Aurora - blue: keywords and tags (frequent tokens, sits back at L57)
"keyword": hl(BLUE),
"tag": hl(BLUE),
"link_text": hl(BLUE, style="italic"),
"label": hl(BLUE_BR),
"title": hl(BLUE_BR, weight=700),
# Aurora - cyan: the callable/type layer (bright = the visual anchors)
"function": hl(CYAN_BR),
"function.definition": hl(CYAN_BR),
"function.method": hl(CYAN_BR),
"function.special.definition": hl(CYAN_BR),
"type": hl(CYAN),
"type.interface": hl(CYAN),
"type.super": hl(CYAN),
"constructor": hl(CYAN),
"enum": hl(CYAN),
"variant": hl(CYAN),
"link_uri": hl(CYAN),
"emphasis": hl(CYAN_BR, style="italic"),
"emphasis.strong": hl(CYAN_BR, weight=700),
# Aurora - green: literal data
"string": hl(GREEN),
"string.escape": hl(GREEN_BR),
"string.regex": hl(GREEN_BR),
"string.special": hl(GREEN_BR),
"string.special.symbol": hl(GREEN_BR),
"text.literal": hl(GREEN),
# Bright blue: scalar literals - distinct from strings, still Aurora
"number": hl(BLUE_BR),
"boolean": hl(BLUE_BR),
"constant": hl(BLUE_BR),
# Dawn - sparing use only
"attribute": hl(MAGENTA_BR),
"variable.special": hl(MAGENTA, style="italic"),
"preproc": hl(MAGENTA),
"embedded": hl(MAGENTA),
}
# Collaboration cursors - Aurora first, Dawn for the tail
PLAYER_COLORS = [BLUE, GREEN, MAGENTA, CYAN, RED_BR, YELLOW, CYAN_BR, MAGENTA_BR]
PLAYERS = [{"cursor": c, "background": c, "selection": alpha(c, 0x3D)}
for c in PLAYER_COLORS]
# ---------------------------------------------------------------------------
# Full style map
# ---------------------------------------------------------------------------
STYLE = {
# --- Base surfaces -----------------------------------------------------
"background.appearance": "opaque",
"background": CHROME,
"surface.background": CHROME,
"elevated_surface.background": ELEVATED,
"panel.background": CHROME,
"status_bar.background": CHROME,
"title_bar.background": CHROME,
"title_bar.inactive_background": CHROME,
"toolbar.background": L15,
"tab_bar.background": CHROME,
"tab.active_background": L15, # active tab fuses with the canvas
"tab.inactive_background": CHROME,
"drop_target.background": alpha(BLUE, 0x33),
# --- Borders -----------------------------------------------------------
"border": L30,
"border.variant": L25,
"border.focused": BLUE,
"border.selected": BLUE,
"border.transparent": TRANSPARENT,
"border.disabled": L25,
"pane.focused_border": BLUE,
"pane_group.border": L25,
"panel.focused_border": BLUE,
"panel.indent_guide": L25,
"panel.indent_guide_active": L40,
"panel.indent_guide_hover": L30,
# --- Interactive elements ----------------------------------------------
"element.background": CHROME,
"element.hover": alpha(L25, 0x99),
"element.active": L30,
"element.selected": alpha(BLUE, 0x33),
"element.disabled": alpha(L25, 0x66),
"ghost_element.background": TRANSPARENT,
"ghost_element.hover": alpha(L25, 0x80),
"ghost_element.active": alpha(L30, 0x99),
"ghost_element.selected": alpha(BLUE, 0x26),
"ghost_element.disabled": TRANSPARENT,
# --- Text --------------------------------------------------------------
"text": L75,
"text.muted": L60,
"text.placeholder": L50,
"text.disabled": L40,
"text.accent": BLUE_BR,
"link_text.hover": BLUE_BR,
# --- Icons -------------------------------------------------------------
"icon": L75,
"icon.muted": L60,
"icon.disabled": L40,
"icon.placeholder": L50,
"icon.accent": CYAN_BR,
# --- Editor ------------------------------------------------------------
"editor.background": L15,
"editor.foreground": L75,
"editor.gutter.background": L15,
"editor.subheader.background": CHROME,
"editor.active_line.background": alpha(L25, 0x59),
"editor.highlighted_line.background": alpha(L25, 0x80),
"editor.line_number": L40,
"editor.active_line_number": L90,
"editor.invisible": L40,
"editor.wrap_guide": alpha(L25, 0x80),
"editor.active_wrap_guide": L40,
"editor.indent_guide": L25,
"editor.indent_guide_active": L40,
"editor.document_highlight.read_background": alpha(BLUE, 0x26),
"editor.document_highlight.write_background": alpha(CYAN_BR, 0x33),
"editor.document_highlight.bracket_background": alpha(BLUE, 0x38),
# Search match uses the bright-yellow highlight accent so it never reads
# as a selection (blue) or a bracket match (cyan).
"search.match_background": alpha(YELLOW_BR, 0x38),
# --- Scrollbar ---------------------------------------------------------
"scrollbar.thumb.background": alpha(L40, 0x66),
"scrollbar.thumb.hover_background": alpha(L50, 0x99),
"scrollbar.thumb.border": TRANSPARENT,
"scrollbar.track.background": TRANSPARENT,
"scrollbar.track.border": TRANSPARENT,
# --- Status ------------------------------------------------------------
"error": RED,
"error.background": alpha(RED, 0x1F),
"error.border": alpha(RED, 0x66),
"warning": YELLOW,
"warning.background": alpha(YELLOW, 0x1F),
"warning.border": alpha(YELLOW, 0x66),
"info": BLUE,
"info.background": alpha(BLUE, 0x1F),
"info.border": alpha(BLUE, 0x66),
"success": GREEN,
"success.background": alpha(GREEN, 0x1F),
"success.border": alpha(GREEN, 0x66),
"hint": CYAN,
"hint.background": alpha(CYAN, 0x1F),
"hint.border": alpha(CYAN, 0x66),
"predictive": L40,
"predictive.background": alpha(L40, 0x1F),
"predictive.border": alpha(L40, 0x66),
# --- Version control ---------------------------------------------------
"created": GREEN,
"created.background": alpha(GREEN, 0x1F),
"created.border": alpha(GREEN, 0x66),
"modified": YELLOW,
"modified.background": alpha(YELLOW, 0x1F),
"modified.border": alpha(YELLOW, 0x66),
"deleted": RED,
"deleted.background": alpha(RED, 0x1F),
"deleted.border": alpha(RED, 0x66),
"renamed": BLUE,
"renamed.background": alpha(BLUE, 0x1F),
"renamed.border": alpha(BLUE, 0x66),
"conflict": RED_BR,
"conflict.background": alpha(RED_BR, 0x1F),
"conflict.border": alpha(RED_BR, 0x66),
"ignored": L50,
"ignored.background": alpha(L50, 0x1F),
"ignored.border": alpha(L50, 0x66),
"hidden": L50,
"hidden.background": alpha(L50, 0x1F),
"hidden.border": alpha(L50, 0x66),
"unreachable": L50,
"unreachable.background": alpha(L50, 0x1F),
"unreachable.border": alpha(L50, 0x66),
# --- Terminal: the 16 ANSI colors, matching wezterm/config.lua ----------
"terminal.background": L15,
"terminal.foreground": L75,
"terminal.bright_foreground": L90,
"terminal.dim_foreground": L50,
"terminal.ansi.background": L15,
"terminal.ansi.black": L15,
"terminal.ansi.red": RED,
"terminal.ansi.green": GREEN,
"terminal.ansi.yellow": YELLOW,
"terminal.ansi.blue": BLUE,
"terminal.ansi.magenta": MAGENTA,
"terminal.ansi.cyan": CYAN,
"terminal.ansi.white": L75,
"terminal.ansi.bright_black": L25,
"terminal.ansi.bright_red": RED_BR,
"terminal.ansi.bright_green": GREEN_BR,
"terminal.ansi.bright_yellow": YELLOW_BR,
"terminal.ansi.bright_blue": BLUE_BR,
"terminal.ansi.bright_magenta": MAGENTA_BR,
"terminal.ansi.bright_cyan": CYAN_BR,
"terminal.ansi.bright_white": L90,
"terminal.ansi.dim_black": lab_mix(L15, L25, 0.5),
"terminal.ansi.dim_red": dim(RED),
"terminal.ansi.dim_green": dim(GREEN),
"terminal.ansi.dim_yellow": dim(YELLOW),
"terminal.ansi.dim_blue": dim(BLUE),
"terminal.ansi.dim_magenta": dim(MAGENTA),
"terminal.ansi.dim_cyan": dim(CYAN),
"terminal.ansi.dim_white": L50,
# --- Rotating accents (rainbow indent guides, etc.) --------------------
"accents": [BLUE, CYAN, GREEN, MAGENTA, YELLOW, CYAN_BR, BLUE_BR, GREEN_BR],
"players": PLAYERS,
"syntax": SYNTAX,
}
THEME = {
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Australis",
"author": "Vuong Hoang",
"themes": [
{
"name": "Australis Dark",
"appearance": "dark",
"style": STYLE,
}
],
}
SCHEMA_URL = "https://zed.dev/schema/themes/v0.2.0.json"
def check_against_schema():
"""Assert full coverage of the schema's style keys. Skipped when offline."""
import urllib.request
# zed.dev rejects urllib's default user-agent with a 403.
request = urllib.request.Request(SCHEMA_URL, headers={"User-Agent": "curl/8"})
try:
with urllib.request.urlopen(request, timeout=10) as response:
schema = json.load(response)
except Exception as exc: # offline, or zed.dev unreachable
print("schema check skipped (%s)" % exc, file=sys.stderr)
return
expected = set(schema["definitions"]["ThemeStyleContent"]["properties"])
missing, extra = expected - set(STYLE), set(STYLE) - expected
if missing or extra:
print("MISSING (%d): %s" % (len(missing), sorted(missing)), file=sys.stderr)
print("EXTRA (%d): %s" % (len(extra), sorted(extra)), file=sys.stderr)
sys.exit(1)
print("all %d schema style keys present" % len(expected), file=sys.stderr)
def main():
check_against_schema()
out_path = sys.argv[1] if len(sys.argv) > 1 else "australis-dark.json"
with open(out_path, "w") as handle:
json.dump(THEME, handle, indent=2)
handle.write("\n")
print("wrote %s (chrome=%s L%.0f, elevated=%s L%.0f)" % (
out_path, CHROME, hex_to_lab(CHROME)[0],
ELEVATED, hex_to_lab(ELEVATED)[0]), file=sys.stderr)
if __name__ == "__main__":
main()