mirror of
https://github.com/vh-pfi/australis.git
synced 2026-08-31 01:15:59 -07:00
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:
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user