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}")