solarized-osaka.zed

Solarized Osaka theme for the Zed editor
git clone https://git.bracken.jp/solarized-osaka.zed.git
Log | Files | Refs

solarized_osaka.py (15488B)


      1 #!/usr/bin/env python
      2 
      3 """
      4 Constructs themes/solarized-osaka.json
      5 
      6 Notes:
      7 - Transparency is supported, just append to hex (e.g. `80` for 50%)
      8 - More info:
      9   - https://zed.dev/docs/themes
     10   - https://zed.dev/docs/extensions/themes
     11   - https://zed.dev/schema/themes/v0.2.0.json
     12 """
     13 
     14 import json
     15 
     16 
     17 def hue2rgb(p, q, t):
     18     if t < 0:
     19         t += 1
     20     if t > 1:
     21         t -= 1
     22     if t < 1 / 6:
     23         return p + (q - p) * 6 * t
     24     if t < 1 / 2:
     25         return q
     26     if t < 2 / 3:
     27         return p + (q - p) * (2 / 3 - t) * 6
     28     return p
     29 
     30 
     31 def mix(hex1, hex2, t=0.5):
     32     """Linearly interpolate between two hex colors. t=0 → hex1, t=1 → hex2."""
     33     r1, g1, b1 = int(hex1[1:3], 16), int(hex1[3:5], 16), int(hex1[5:7], 16)
     34     r2, g2, b2 = int(hex2[1:3], 16), int(hex2[3:5], 16), int(hex2[5:7], 16)
     35     r, g, b = round(r1 + (r2 - r1) * t), round(g1 + (g2 - g1) * t), round(b1 + (b2 - b1) * t)
     36     return f"#{r:02x}{g:02x}{b:02x}"
     37 
     38 
     39 def hsl(h, s, l):
     40     """Convert HSL (h: 0-360, s: 0-100, l: 0-100) to hex string."""
     41     h, s, l = h / 360, s / 100, l / 100
     42     if s == 0:
     43         v = round(l * 255)
     44         return f"#{v:02x}{v:02x}{v:02x}"
     45     q = l * (1 + s) if l < 0.5 else l + s - l * s
     46     p = 2 * l - q
     47     r = hue2rgb(p, q, h + 1 / 3)
     48     g = hue2rgb(p, q, h)
     49     b = hue2rgb(p, q, h - 1 / 3)
     50     return f"#{round(r * 255):02x}{round(g * 255):02x}{round(b * 255):02x}"
     51 
     52 
     53 # Solarized Osaka base colors — base04 (darkest) through base4 (white)
     54 _base04 = hsl(192, 100, 5)   # darkest background (sidebars)
     55 _base03 = hsl(192, 100, 11)  # dark background
     56 _base02 = hsl(192, 81, 14)   # alt background
     57 _base01 = hsl(194, 14, 40)   # secondary content (dark); emphasized (light)
     58 _base00 = hsl(196, 13, 45)   # body text (light mode)
     59 _base0  = hsl(186, 8, 65)    # body text (dark mode)
     60 _base1  = hsl(180, 7, 70)    # emphasized (dark); secondary (light)
     61 _base2  = hsl(46, 42, 88)    # light background highlights
     62 _base3  = hsl(44, 87, 94)    # light background
     63 _base4  = hsl(0, 0, 100)     # white
     64 
     65 # Accent colors — 500 (main), 700 (darker), 300 (brighter)
     66 _yellow     = hsl(45, 100, 35)
     67 _yellow300  = hsl(45, 100, 50)
     68 _yellow700  = hsl(45, 100, 20)
     69 _orange     = hsl(18, 80, 44)
     70 _red        = hsl(1, 71, 52)
     71 _red300     = hsl(1, 90, 64)
     72 _red700     = hsl(1, 71, 42)
     73 _magenta    = hsl(331, 64, 52)
     74 _magenta300 = hsl(331, 86, 64)
     75 _magenta700 = hsl(331, 64, 42)
     76 _violet     = hsl(237, 43, 60)
     77 _blue       = hsl(205, 69, 49)
     78 _blue300    = hsl(205, 90, 62)
     79 _blue700    = hsl(205, 70, 35)
     80 _cyan       = hsl(175, 59, 40)
     81 _cyan300    = hsl(175, 85, 55)
     82 _cyan700    = hsl(182, 59, 25)
     83 _green      = hsl(68, 100, 30)
     84 _green300   = hsl(76, 100, 49)
     85 _green700   = hsl(68, 100, 20)
     86 
     87 # Dark variant: deep teal backgrounds, muted blue-green foregrounds
     88 osaka_dark = {
     89     "base04": _base04, "base03": _base03, "base02": _base02,
     90     "base01": _base01, "base00": _base00, "base0": _base0,
     91     "base1": _base1, "base2": _base2, "base3": _base3, "base4": _base4,
     92     "yellow": _yellow, "yellow300": _yellow300, "yellow700": _yellow700,
     93     "orange": _orange,
     94     "red": _red, "red300": _red300, "red700": _red700,
     95     "magenta": _magenta, "magenta300": _magenta300, "magenta700": _magenta700,
     96     "violet": _violet,
     97     "blue": _blue, "blue300": _blue300, "blue700": _blue700,
     98     "cyan": _cyan, "cyan300": _cyan300, "cyan700": _cyan700,
     99     "green": _green, "green300": _green300, "green700": _green700,
    100     "bg1": _base04,        # editor pane background
    101     "bg2": _base03,        # active line, hover, highlight surfaces
    102     "bg_chrome": mix(_base04, _base03, 1/3),  # chrome bars — 1/3 of the way from editor to highlight
    103     "fg1": _base0,   # body text / default code / primary content
    104     "fg2": _base01,  # comments / secondary content
    105     "fg3": _base1,   # optional emphasized content
    106 }
    107 
    108 # Light variant: inverted base scale, inverted accent brightness, same hues
    109 osaka_light = {
    110     **osaka_dark,
    111     "base04": _base4, "base03": _base3, "base02": _base2,
    112     "base01": _base1, "base00": _base0,
    113     "base0": _base00, "base1": _base01,
    114     "base2": _base02, "base3": _base03, "base4": _base04,
    115     # Accent variants inverted (300 ↔ 700) for readability on light backgrounds
    116     "yellow300": _yellow700, "yellow700": _yellow300,
    117     "red300": _red700, "red700": _red300,
    118     "magenta300": _magenta700, "magenta700": _magenta300,
    119     "blue300": _blue700, "blue700": _blue300,
    120     "cyan300": _cyan700, "cyan700": _cyan300,
    121     "green300": _green700, "green700": _green300,
    122     "bg1": _base3,        # editor pane background
    123     "bg2": _base2,        # active line, hover, highlight surfaces
    124     "bg_chrome": mix(_base3, _base2, 1/3),  # chrome bars — 1/3 of the way from editor to highlight
    125     "fg1": _base00,  # body text / default code / primary content
    126     "fg2": _base1,   # comments / secondary content
    127     "fg3": _base01,  # optional emphasized content
    128 }
    129 
    130 
    131 def osaka_theme(c):
    132     players = [
    133         {"background": c[color], "cursor": c[color], "selection": c[color] + "66"}
    134         for color in ["blue", "cyan", "green", "magenta", "orange", "red", "violet", "yellow"]
    135     ]
    136     syntax = {
    137         "attribute": {"color": c["cyan"]},
    138         "boolean": {"color": c["yellow"]},
    139         "comment": {"color": c["fg2"], "font_style": "italic"},
    140         "comment.doc": {"color": c["fg2"], "font_style": "italic"},
    141         "constant": {"color": c["cyan"]},
    142         "constructor": {"color": c["blue"]},
    143         "embedded": {"color": c["fg1"]},
    144         "emphasis": {"color": c["blue"]},
    145         "emphasis.strong": {"color": c["blue"], "font_weight": 700},
    146         "enum": {"color": c["orange"]},
    147         "function": {"color": c["blue"]},
    148         "hint": {"color": c["cyan"], "font_weight": 700},
    149         "keyword": {"color": c["green"]},
    150         "label": {"color": c["blue"]},
    151         "link_text": {"color": c["blue"], "font_style": "italic"},
    152         "link_uri": {"color": c["violet"]},
    153         "number": {"color": c["magenta"]},
    154         "operator": {"color": c["cyan"]},
    155         "predictive": {"background_color": c["bg1"], "color": c["fg2"]},
    156         "preproc": {"color": c["orange"]},
    157         "primary": {"color": c["fg1"]},
    158         "property": {"color": c["blue"]},
    159         "punctuation": {"color": c["fg2"]},
    160         "punctuation.bracket": {"color": c["fg2"]},
    161         "punctuation.delimiter": {"color": c["fg2"]},
    162         "punctuation.list_marker": {"color": c["fg2"]},
    163         "punctuation.special": {"color": c["cyan"]},
    164         "string": {"color": c["cyan"]},
    165         "string.escape": {"color": c["orange"]},
    166         "string.regex": {"color": c["orange"]},
    167         "string.special": {"color": c["orange"]},
    168         "string.special.symbol": {"color": c["magenta"]},
    169         "tag": {"color": c["red"]},
    170         "text.literal": {"color": c["cyan"]},
    171         "title": {"color": c["orange"], "font_weight": 700},
    172         "type": {"color": c["yellow"]},
    173         "variable": {"color": c["fg1"]},
    174         "variable.special": {"color": c["orange"]},
    175         "variant": {"color": c["blue"]},
    176     }
    177     return {
    178         # All main accent hues for the accent strip
    179         "accents": [c["red"], c["orange"], c["yellow"], c["green"], c["cyan"], c["blue"], c["magenta"]],
    180         "background": c["bg1"],
    181         # Opaque background — no bleed-through from wallpaper
    182         "background.appearance": "opaque",
    183         # Borders are subtle (bg2), not the prominent fg2 of stock Solarized
    184         "border": c["bg2"],
    185         "border.disabled": c["bg2"],
    186         "border.focused": None,
    187         "border.selected": c["bg2"],
    188         "border.transparent": c["bg2"],
    189         "border.variant": c["bg2"],
    190         # Git / diff status
    191         "conflict": c["violet"],
    192         "conflict.background": c["bg1"],
    193         "conflict.border": c["violet"],
    194         "created": c["green"],
    195         "created.background": c["bg1"],
    196         "created.border": c["green"],
    197         "deleted": c["red"],
    198         "deleted.background": c["bg1"],
    199         "deleted.border": c["red"],
    200         "modified": c["blue"],
    201         "modified.background": c["bg1"],
    202         "modified.border": c["blue"],
    203         "renamed": c["magenta"],
    204         "renamed.background": c["bg1"] + "00",
    205         "renamed.border": c["magenta"],
    206         # Drop / drag
    207         "drop_target.background": c["bg2"],
    208         # Editor pane
    209         "editor.active_line.background": c["bg2"] + "cb",
    210         "editor.active_line_number": c["fg1"],
    211         "editor.active_wrap_guide": c["bg2"],
    212         "editor.background": c["bg1"],
    213         "editor.document_highlight.bracket_background": c["bg2"] + "66",
    214         "editor.document_highlight.read_background": c["blue"] + "33",
    215         "editor.document_highlight.write_background": c["blue"] + "33",
    216         "editor.foreground": c["fg1"],
    217         "editor.gutter.background": c["bg1"],
    218         "editor.highlighted_line.background": c["bg1"],
    219         "editor.indent_guide": c["bg2"],
    220         "editor.indent_guide_active": c["cyan"],
    221         "editor.invisible": c["bg2"],
    222         "editor.line_number": c["fg2"],
    223         "editor.subheader.background": c["bg1"],
    224         "editor.wrap_guide": c["bg2"],
    225         # Interactive elements — cyan700 gives a distinctive teal pop for selected items
    226         "element.active": c["bg2"],
    227         "element.background": c["cyan700"],
    228         "element.disabled": c["bg2"],
    229         "element.hover": c["bg2"],
    230         "element.selected": c["bg2"],
    231         # Surfaces — bg_chrome sits between editor (bg1) and highlights (bg2)
    232         "elevated_surface.background": c["bg_chrome"],
    233         "surface.background": c["bg_chrome"],
    234         # Diagnostics — transparent backgrounds so they don't wash over code
    235         "error": c["red"],
    236         "error.background": c["red"] + "00",
    237         "error.border": "#ffffff00",
    238         "warning": c["yellow"],
    239         "warning.background": c["yellow"] + "00",
    240         "warning.border": "#ffffff00",
    241         "info": c["blue"],
    242         "info.background": c["blue"] + "00",
    243         "info.border": "#ffffff00",
    244         "success": c["green"],
    245         "success.background": c["bg1"] + "00",
    246         "success.border": c["green"],
    247         "hint": "#969696ff",
    248         "hint.background": c["bg1"] + "00",
    249         "hint.border": c["fg2"],
    250         # Ghost elements (buttons, hover states)
    251         "ghost_element.active": c["fg1"],
    252         "ghost_element.background": c["bg2"],
    253         "ghost_element.disabled": c["fg2"],
    254         "ghost_element.hover": c["bg2"],
    255         "ghost_element.selected": c["bg2"],
    256         # Hidden / ignored files
    257         "hidden": c["fg1"] + "80",
    258         "hidden.background": c["bg1"],
    259         "hidden.border": c["bg1"],
    260         "ignored": c["fg1"],
    261         "ignored.background": c["bg1"],
    262         "ignored.border": c["fg2"],
    263         # Icons
    264         "icon": c["fg1"],
    265         "icon.accent": c["blue"],
    266         "icon.disabled": c["fg2"],
    267         "icon.muted": c["fg2"],
    268         "icon.placeholder": c["fg2"],
    269         # Links
    270         "link_text.hover": c["blue"],
    271         # Panes and panels — all flush with bg1 so chrome disappears
    272         "pane.focused_border": c["blue"],
    273         "pane_group.border": c["bg2"],
    274         "panel.background": c["bg_chrome"],
    275         "panel.focused_border": c["blue"],
    276         "panel.indent_guide": c["fg2"],
    277         "panel.indent_guide_active": c["cyan"],
    278         "panel.indent_guide_hover": c["fg1"],
    279         # Players (collaborative cursors)
    280         "players": players,
    281         # AI predictive text
    282         "predictive": c["magenta"],
    283         "predictive.background": c["bg1"] + "00",
    284         "predictive.border": c["magenta"],
    285         # Scrollbar — neutral grays so it doesn't pull focus
    286         "scrollbar.thumb.background": "#79797966",
    287         "scrollbar.thumb.border": "#79797966",
    288         "scrollbar.thumb.hover_background": "#646464b3",
    289         "scrollbar.track.background": c["bg_chrome"],
    290         "scrollbar.track.border": "#7f7f7f4d",
    291         # Search
    292         "search.match_background": c["orange"] + "56",
    293         # Chrome bars — bg_chrome (between editor and highlight); active tab uses bg1 to connect to the editor pane
    294         "status_bar.background": c["bg_chrome"],
    295         "tab.active_background": c["bg1"],
    296         "tab.inactive_background": c["bg_chrome"],
    297         "tab_bar.background": c["bg_chrome"],
    298         "title_bar.background": c["bg_chrome"],
    299         "title_bar.inactive_background": c["bg_chrome"],
    300         "toolbar.background": c["bg1"],
    301         # Syntax
    302         "syntax": syntax,
    303         # Terminal — standard dark/bright ANSI pairs using Osaka accent variants
    304         "terminal.ansi.background": c["bg1"],
    305         "terminal.ansi.black": "#000000",
    306         "terminal.ansi.bright_black": "#666666",
    307         "terminal.ansi.red": c["red700"],
    308         "terminal.ansi.bright_red": c["red"],
    309         "terminal.ansi.green": c["green"],
    310         "terminal.ansi.bright_green": c["green300"],
    311         "terminal.ansi.yellow": c["yellow"],
    312         "terminal.ansi.bright_yellow": c["yellow300"],
    313         "terminal.ansi.blue": c["blue700"],
    314         "terminal.ansi.bright_blue": c["blue"],
    315         "terminal.ansi.magenta": c["magenta700"],
    316         "terminal.ansi.bright_magenta": c["magenta"],
    317         "terminal.ansi.cyan": c["cyan"],
    318         "terminal.ansi.bright_cyan": c["cyan300"],
    319         "terminal.ansi.white": c["base2"],
    320         "terminal.ansi.bright_white": c["base3"],
    321         "terminal.ansi.dim_black": "#000000",
    322         "terminal.ansi.dim_blue": c["blue700"],
    323         "terminal.ansi.dim_cyan": c["cyan700"],
    324         "terminal.ansi.dim_green": c["green"],
    325         "terminal.ansi.dim_magenta": c["magenta700"],
    326         "terminal.ansi.dim_red": c["red700"],
    327         "terminal.ansi.dim_white": c["base2"],
    328         "terminal.ansi.dim_yellow": c["yellow"],
    329         "terminal.background": c["bg1"],
    330         "terminal.bright_foreground": c["fg3"],
    331         "terminal.dim_foreground": c["fg2"],
    332         "terminal.foreground": c["fg1"],
    333         # Text
    334         "text": c["fg1"],
    335         "text.accent": c["fg3"],
    336         "text.disabled": c["fg2"],
    337         "text.muted": c["fg1"] + "80",
    338         "text.placeholder": c["fg2"],
    339         # Unreachable code
    340         "unreachable": c["violet"],
    341         "unreachable.background": c["bg1"],
    342         "unreachable.border": c["violet"],
    343         # Vim mode indicator colors
    344         "vim.normal.background": c["blue"],
    345         "vim.normal.foreground": c["base04"],
    346         "vim.insert.background": c["green"],
    347         "vim.insert.foreground": c["base04"],
    348         "vim.replace.background": c["red"],
    349         "vim.replace.foreground": c["base04"],
    350         "vim.visual.background": c["magenta"],
    351         "vim.visual.foreground": c["base04"],
    352         "vim.visual_line.background": c["violet"],
    353         "vim.visual_line.foreground": c["base04"],
    354         "vim.visual_block.background": c["yellow"],
    355         "vim.visual_block.foreground": c["base04"],
    356         "vim.yank.background": c["fg1"] + "0b",
    357     }
    358 
    359 
    360 theme_dark = {
    361     "appearance": "dark",
    362     "name": "Solarized Osaka Dark",
    363     "style": osaka_theme(osaka_dark),
    364 }
    365 print("Added Solarized Osaka Dark")
    366 
    367 theme_light = {
    368     "appearance": "light",
    369     "name": "Solarized Osaka Light",
    370     "style": osaka_theme(osaka_light),
    371 }
    372 print("Added Solarized Osaka Light")
    373 
    374 output = {
    375     "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
    376     "author": "cbracken",
    377     "name": "Solarized Osaka",
    378     "themes": [theme_dark, theme_light],
    379 }
    380 
    381 json_file = "themes/solarized-osaka.json"
    382 with open(json_file, "w") as f:
    383     json.dump(output, f, indent=2)
    384     print(f"Wrote to {json_file}")