diff --git a/aerospace/.aerospace.toml.swp b/aerospace/.aerospace.toml.swp
new file mode 100644
index 0000000..eaa1ffe
Binary files /dev/null and b/aerospace/.aerospace.toml.swp differ
diff --git a/aerospace/aerospace.toml b/aerospace/aerospace.toml
new file mode 100644
index 0000000..8e928ce
--- /dev/null
+++ b/aerospace/aerospace.toml
@@ -0,0 +1,223 @@
+# Place a copy of this config to ~/.aerospace.toml
+
+
+# After that, you can edit ~/.aerospace.toml to your liking
+
+# You can use it to add commands that run after login to macOS user session.
+# 'start-at-login' needs to be 'true' for 'after-login-command' to work
+# Available commands: https://nikitabobko.github.io/AeroSpace/commands
+after-login-command = []
+
+# You can use it to add commands that run after AeroSpace startup.
+# 'after-startup-command' is run after 'after-login-command'
+# Available commands : https://nikitabobko.github.io/AeroSpace/commands
+after-startup-command = [
+ 'exec-and-forget borders active_color=0xffcba6f7 inactive_color=0xff585b70 width=5.0'
+]
+
+# Notify Sketchybar about workspace change
+exec-on-workspace-change = ['/bin/bash', '-c',
+ 'sketchybar --trigger aerospace_workspace_change FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE'
+]
+
+
+# Start AeroSpace at login
+start-at-login = true
+
+# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization
+enable-normalization-flatten-containers = true
+enable-normalization-opposite-orientation-for-nested-containers = true
+
+# See: https://nikitabobko.github.io/AeroSpace/guide#layouts
+# The 'accordion-padding' specifies the size of accordion padding
+# You can set 0 to disable the padding feature
+accordion-padding = 50
+
+# Possible values: tiles|accordion
+default-root-container-layout = 'tiles'
+
+# Possible values: horizontal|vertical|auto
+# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation,
+# tall monitor (anything higher than wide) gets vertical orientation
+default-root-container-orientation = 'auto'
+
+# Mouse follows focus when focused monitor changes
+# Drop it from your config, if you don't like this behavior
+# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
+# See https://nikitabobko.github.io/AeroSpace/commands#move-mouse
+# Fallback value (if you omit the key): on-focused-monitor-changed = []
+on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
+on-focus-changed = "move-mouse window-lazy-center"
+
+
+# You can effectively turn off macOS "Hide application" (cmd-h) feature by toggling this flag
+# Useful if you don't use this macOS feature, but accidentally hit cmd-h or cmd-alt-h key
+# Also see: https://nikitabobko.github.io/AeroSpace/goodies#disable-hide-app
+automatically-unhide-macos-hidden-apps = false
+
+# Possible values: (qwerty|dvorak|colemak)
+# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
+[key-mapping]
+ preset = 'qwerty'
+
+# Gaps between windows (inner-*) and between monitor edges (outer-*).
+# Possible values:
+# - Constant: gaps.outer.top = 8
+# - Per monitor: gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24]
+# In this example, 24 is a default value when there is no match.
+# Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'.
+# See:
+# https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors
+[gaps]
+ inner.horizontal = 20
+ inner.vertical = 20
+ outer.left = 20
+ outer.bottom = 20
+ outer.top = 40
+ outer.right = 20
+
+# 'main' binding mode declaration
+# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
+# 'main' binding mode must be always presented
+# Fallback value (if you omit the key): mode.main.binding = {}
+[mode.main.binding]
+
+ # All possible keys:
+ # - Letters. a, b, c, ..., z
+ # - Numbers. 0, 1, 2, ..., 9
+ # - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9
+ # - F-keys. f1, f2, ..., f20
+ # - Special keys. minus, equal, period, comma, slash, backslash, quote, semicolon,
+ # backtick, leftSquareBracket, rightSquareBracket, space, enter, esc,
+ # backspace, tab, pageUp, pageDown, home, end, forwardDelete,
+ # sectionSign (ISO keyboards only, european keyboards only)
+ # - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual,
+ # keypadMinus, keypadMultiply, keypadPlus
+ # - Arrows. left, down, up, right
+
+ # All possible modifiers: cmd, alt, ctrl, shift
+
+ # All possible commands: https://nikitabobko.github.io/AeroSpace/commands
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#exec-and-forget
+ # You can uncomment the following lines to open up terminal with alt + enter shortcut
+ # (like in i3)
+ # alt-enter = '''exec-and-forget osascript -e '
+ # tell application "Terminal"
+ # do script
+ # activate
+ # end tell'
+ # '''
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#layout
+ alt-period = 'layout tiles horizontal vertical'
+ alt-comma = 'layout accordion horizontal vertical'
+ alt-enter = 'exec-and-forget open -n /Applications/Ghostty.app'
+ alt-shift-s = 'exec-and-forget screencapture -i -c'
+
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#focus
+ alt-h = 'focus left'
+ alt-left = 'focus left'
+ alt-j = 'focus down'
+ alt-down = 'focus down'
+ alt-k = 'focus up'
+ alt-up = 'focus up'
+ alt-l = 'focus right'
+ alt-right = 'focus right'
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#move
+ alt-shift-left = 'move left'
+ alt-shift-down = 'move down'
+ alt-shift-up = 'move up'
+ alt-shift-right = 'move right'
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#resize
+ alt-9 = 'resize smart -50'
+ alt-0 = 'resize smart +50'
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#workspace
+ alt-1 = 'workspace 1'
+ alt-2 = 'workspace 2'
+ alt-3 = 'workspace 3'
+ alt-4 = 'workspace 4'
+ alt-5 = 'workspace 5'
+ alt-6 = 'workspace 6'
+ alt-d = 'workspace D'
+ alt-s = 'workspace S'
+ alt-g = 'workspace G'
+ # See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace
+ alt-shift-1 = 'move-node-to-workspace 1'
+ alt-shift-2 = 'move-node-to-workspace 2'
+ alt-shift-3 = 'move-node-to-workspace 3'
+ alt-shift-4 = 'move-node-to-workspace 4'
+ alt-shift-5 = 'move-node-to-workspace 5'
+ alt-shift-6 = 'move-node-to-workspace 6'
+ alt-a= 'layout floating tiling'
+ alt-shift-f = 'fullscreen'
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#workspace-back-and-forth
+ alt-tab = 'workspace-back-and-forth'
+ # See: https://nikitabobko.github.io/AeroSpace/commands#move-workspace-to-monitor
+ alt-shift-tab = 'move-workspace-to-monitor --wrap-around next'
+
+ # See: https://nikitabobko.github.io/AeroSpace/commands#mode
+ alt-shift-q = 'mode service'
+
+# 'service' binding mode declaration.
+# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
+[mode.service.binding]
+ esc = ['reload-config', 'mode main']
+ r = ['flatten-workspace-tree', 'mode main'] # reset layout
+ f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout
+ backspace = ['close-all-windows-but-current', 'mode main']
+
+ # sticky is not yet supported https://github.com/nikitabobko/AeroSpace/issues/2
+ #s = ['layout sticky tiling', 'mode main']
+
+ alt-shift-h = ['join-with left', 'mode main']
+ alt-shift-j = ['join-with down', 'mode main']
+ alt-shift-k = ['join-with up', 'mode main']
+ alt-shift-l = ['join-with right', 'mode main']
+
+[[on-window-detected]]
+if.app-id = 'com.hnc.Discord'
+run = "move-node-to-workspace D"
+
+[[on-window-detected]]
+if.app-id = 'com.spotify.client'
+run = "move-node-to-workspace S"
+
+[[on-window-detected]]
+if.app-id = 'com.mitchellh.ghostty'
+run = "move-node-to-workspace 2"
+
+[[on-window-detected]]
+if.app-id = 'com.bitwarden.desktop'
+run = "move-node-to-workspace 5"
+
+[[on-window-detected]]
+if.app-id = 'ch.protonvpn.mac'
+run = "move-node-to-workspace 5"
+
+[[on-window-detected]]
+if.app-id = 'io.xpipe.app'
+run = "move-node-to-workspace 3"
+
+ [[on-window-detected]]
+if.app-id = 'com.apple.finder'
+run = [
+'layout floating'
+]
+
+[[on-window-detected]]
+if.app-id = 'com.setapp.DesktopClient'
+run = [
+ 'layout floating'
+]
+
+[[on-window-detected]]
+if.app-id = 'com.apple.MobileSMS'
+run = [
+ 'layout floating'
+]
diff --git a/ghostty/.DS_Store b/ghostty/.DS_Store
new file mode 100644
index 0000000..289c0e7
Binary files /dev/null and b/ghostty/.DS_Store differ
diff --git a/ghostty/config b/ghostty/config
new file mode 100644
index 0000000..bc9bd75
--- /dev/null
+++ b/ghostty/config
@@ -0,0 +1,288 @@
+# To get the default values
+# ghostty +show-config --default --docs > ~/github/dotfiles-latest/ghostty/config-default
+
+# To get syntax highlighting in neovim, see
+# ~/github/dotfiles-latest/neovim/neobean/lua/plugins/ghostty.lua
+
+# HACK: How to setup the Ghostty terminal, is it just hype? READ PINNED MESSAGE
+# https://youtu.be/rCiq5CyFFhA
+
+# No way of reloading config via command yet, only via keymap cmd+shift+,
+# Created a script to do it so that its reloaded with my colorscheme selector
+
+# Additional configuration files to read. This configuration can be repeated
+# to read multiple configuration files. Configuration files themselves can
+# load more configuration files. Paths are relative to the file containing the
+# `config-file` directive. For command-line arguments, paths are relative to
+# the current working directory.
+#
+# Prepend a ? character to the file path to suppress errors if the file does
+# not exist. If you want to include a file that begins with a literal ?
+# character, surround the file path in double quotes (").
+#
+
+# Cycles are not allowed. If a cycle is detected, an error will be logged and
+# the configuration file will be ignored.
+# config-file = ghostty-theme
+theme = Catppuccin Mocha
+cursor-color = #cba6f7
+
+# Some shaders can be found in this repo, they're usually uploaded to discord
+# https://github.com/hackrmomo/ghostty-shaders
+# custom-shader = shaders/animated-gradient-shader.glsl
+# custom-shader = shaders/bettercrt.glsl
+# custom-shader = shaders/bloom.glsl
+# custom-shader = shaders/bloom1.glsl
+# custom-shader = shaders/bloom075.glsl
+# custom-shader = shaders/bloom060.glsl
+# custom-shader = shaders/bloom050.glsl
+# custom-shader = shaders/bloom025.glsl
+# custom-shader = shaders/bloom050.glsl
+custom-shader = shaders/bloom060.glsl
+# custom-shader = shaders/bloom075.glsl
+# custom-shader = shaders/bloom1.glsl
+# custom-shader = shaders/crt.glsl
+# custom-shader = shaders/cubes.glsl
+# custom-shader = shaders/dither.glsl
+# custom-shader = shaders/drunkard.glsl
+# custom-shader = shaders/fireworks-rockets.glsl
+# custom-shader = shaders/fireworks.glsl
+# custom-shader = shaders/gears-and-belts.glsl
+# custom-shader = shaders/glitchy.glsl
+# custom-shader = shaders/glow-rgbsplit-twitchy.glsl
+# custom-shader = shaders/gradient-background.glsl
+# custom-shader = shaders/inside-the-matrix.glsl
+# custom-shader = shaders/just-snow.glsl
+# custom-shader = shaders/matrix-hallway.glsl
+# custom-shader = shaders/negative.glsl
+# custom-shader = shaders/retro-terminal.glsl
+# custom-shader = shaders/smoke-and-ghost.glsl
+# custom-shader = shaders/sparks-from-fire.glsl
+# custom-shader = shaders/spotlight.glsl
+# custom-shader = shaders/starfield-colors.glsl
+# custom-shader = shaders/starfield.glsl
+# custom-shader = shaders/tft.glsl
+# custom-shader = shaders/underwater.glsl
+# custom-shader = shaders/water.glsl
+# custom-shader = shaders/cursor_blaze.glsl
+custom-shader = shaders/cursor_blaze_no_trail.glsl
+# custom-shader = shaders/cursor_smear.glsl
+# custom-shader = shaders/cursor_smear_fade.glsl
+custom-shader = shaders/cursor_warp.glsl
+custom-shader = shaders/ripple_cursor.glsl
+
+# The command to run, usually a shell. If this is not an absolute path, it'll
+# be looked up in the `PATH`. If this is not set, a default will be looked up
+# from your system. The rules for the default lookup are:
+#
+# * `SHELL` environment variable
+#
+# * `passwd` entry (user information)
+#
+# This can contain additional arguments to run the command with. If additional
+# arguments are provided, the command will be executed using `/bin/sh -c`.
+# Ghostty does not do any shell command parsing.
+#
+# If you're using the `ghostty` CLI there is also a shortcut to run a command
+# with arguments directly: you can use the `-e` flag. For example: `ghostty -e
+# fish --with --custom --args`. The `-e` flag automatically forces some
+# other behaviors as well:
+#
+# * `gtk-single-instance=false` - This ensures that a new instance is
+# launched and the CLI args are respected.
+#
+# * `quit-after-last-window-closed=true` - This ensures that the Ghostty
+# process will exit when the command exits. Additionally, the
+# `quit-after-last-window-closed-delay` is unset.
+# ############################################################################
+# The following command checks if tmux is installed.
+# If tmux is installed, it automatically attaches to an existing tmux session.
+# If no tmux session exists, a new one is created.
+# If tmux is not installed, it simply starts zsh without tmux.
+#
+# For this to work properly, you need to make sure that your shell is configured
+# for homebrew, so you should have this line:
+# eval "$(/opt/homebrew/bin/brew shellenv)"
+# In your ~/.zprofile file
+# If you don't have that line, or if the file doesn't exist, run this:
+# echo '' >>~/.zprofile && echo '# Configure shell for brew' >>~/.zprofile && echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >>~/.zprofile && eval "$(/opt/homebrew/bin/brew shellenv)"
+#
+# This assumes you installed tmux through brew if using macos
+# command = zsh --login -c "if command -v tmux >/dev/null 2>&1; then tmux attach || tmux; else zsh; fi"
+# command = zsh --login -c "if command -v tmux >/dev/null 2>&1; then $HOME/github/dotfiles-latest/tmux/tools/prime/tmux-sessionizer.sh $HOME/github/dotfiles-latest || (tmux attach 2>/dev/null || tmux); else exec zsh; fi"
+
+# Horizontal window padding. This applies padding between the terminal cells
+# and the left and right window borders. The value is in points, meaning that
+# it will be scaled appropriately for screen DPI.
+#
+# If this value is set too large, the screen will render nothing, because the
+# grid will be completely squished by the padding. It is up to you as the user
+# to pick a reasonable value. If you pick an unreasonable value, a warning
+# will appear in the logs.
+#
+# Changing this configuration at runtime will only affect new terminals, i.e.
+# new windows, tabs, etc.
+#
+# To set a different left and right padding, specify two numerical values
+# separated by a comma. For example, `window-padding-x = 2,4` will set the
+# left padding to 2 and the right padding to 4. If you want to set both
+# paddings to the same value, you can use a single value. For example,
+# `window-padding-x = 2` will set both paddings to 2.
+window-padding-x = 4,2
+
+# Vertical window padding. This applies padding between the terminal cells and
+# the top and bottom window borders. The value is in points, meaning that it
+# will be scaled appropriately for screen DPI.
+#
+# If this value is set too large, the screen will render nothing, because the
+# grid will be completely squished by the padding. It is up to you as the user
+# to pick a reasonable value. If you pick an unreasonable value, a warning
+# will appear in the logs.
+#
+# Changing this configuration at runtime will only affect new terminals,
+# i.e. new windows, tabs, etc.
+#
+# To set a different top and bottom padding, specify two numerical values
+# separated by a comma. For example, `window-padding-y = 2,4` will set the
+# top padding to 2 and the bottom padding to 4. If you want to set both
+# paddings to the same value, you can use a single value. For example,
+# `window-padding-y = 2` will set both paddings to 2.
+window-padding-y = 6,0
+
+font-family = FiraCode Nerd Font
+font-size = 15
+
+# Valid values:
+#
+# * `true`
+# * `false` - windows won't have native decorations, i.e. titlebar and
+# borders. On macOS this also disables tabs and tab overview.
+#
+# The "toggle_window_decoration" keybind action can be used to create
+# a keybinding to toggle this setting at runtime.
+#
+# Changing this configuration in your configuration and reloading will
+# only affect new windows. Existing windows will not be affected.
+#
+# macOS: To hide the titlebar without removing the native window borders
+# or rounded corners, use `macos-titlebar-style = hidden` instead.
+macos-titlebar-style = hidden
+
+# Confirms that a surface should be closed before closing it. This defaults to
+# true. If set to false, surfaces will close without any confirmation.
+confirm-close-surface = false
+
+# Toggle the "quick" terminal. The quick terminal is a terminal that
+# appears on demand from a keybinding, often sliding in from a screen
+# edge such as the top. This is useful for quick access to a terminal
+# without having to open a new window or tab.
+#
+# When the quick terminal loses focus, it disappears. The terminal state
+# is preserved between appearances, so you can always press the keybinding
+# to bring it back up.
+#
+# The quick terminal has some limitations:
+#
+# - It is a singleton; only one instance can exist at a time.
+# - It does not support tabs.
+# - It will not be restored when the application is restarted
+# (for systems that support window restoration).
+# - It supports fullscreen, but fullscreen will always be a non-native
+# fullscreen (macos-non-native-fullscreen = true). This only applies
+# to the quick terminal window. This is a requirement due to how
+# the quick rerminal is rendered.
+#
+# See the various configurations for the quick terminal in the
+# configuration file to customize its behavior.
+#
+# `global:` - Make the keybind global. By default, keybinds only work
+# within Ghostty and under the right conditions (application focused,
+# sometimes terminal focused, etc.). If you want a keybind to work
+# globally across your system (i.e. even when Ghostty is not focused),
+# specify this prefix. This prefix implies `all:`. Note: this does not
+# work in all environments; see the additional notes below for more
+# information.
+# keybind = global:cmd+s=toggle_quick_terminal
+# keybind = cmd+s=toggle_quick_terminal
+
+# If `true`, the *Option* key will be treated as *Alt*. This makes terminal
+# sequences expecting *Alt* to work properly, but will break Unicode input
+# sequences on macOS if you use them via the *Alt* key. You may set this to
+# `false` to restore the macOS *Alt* key unicode sequences but this will break
+# terminal sequences expecting *Alt* to work.
+#
+# The values `left` or `right` enable this for the left or right *Option*
+# key, respectively.
+#
+# Note that if an *Option*-sequence doesn't produce a printable character, it
+# will be treated as *Alt* regardless of this setting. (i.e. `alt+ctrl+a`).
+#
+# This does not work with GLFW builds.
+macos-option-as-alt = right
+
+# Duration (in seconds) of the quick terminal enter and exit animation.
+# Set it to 0 to disable animation completely. This can be changed at
+# runtime.
+# quick-terminal-animation-duration = 0.1
+quick-terminal-animation-duration = 0.08
+# quick-terminal-animation-duration = 0
+
+keybind = super+i=inspector:toggle
+keybind = super+r=reload_config
+
+# This doesn't work when in tmux, if outside tmux the regular ctrl+l works
+# keybind = super+k=clear_screen
+
+# When not running tmux I see what C-enter sends
+# I can do `/bin/cat -v` and then pressed C-enter
+# Ghostty sends: ^[[27;5;13~
+#
+# The problem is that when I run tmux, nothing is sent, so I'm sending those
+# keys in my tmux.conf file
+
+# Customize the macOS app icon.
+#
+# This only affects the icon that appears in the dock, application
+# switcher, etc. This does not affect the icon in Finder because
+# that is controlled by a hardcoded value in the signed application
+# bundle and can't be changed at runtime. For more details on what
+# exactly is affected, see the `NSApplication.icon` Apple documentation;
+# that is the API that is being used to set the icon.
+#
+# WARNING: The `custom-style` option is _experimental_. We may change
+# the format of the custom styles in the future. We're still finalizing
+# the exact layers and customization options that will be available.
+#
+# Other caveats:
+#
+# * The icon in the update dialog will always be the official icon.
+# This is because the update dialog is managed through a
+# separate framework and cannot be customized without significant
+# effort.
+#
+# Valid values:
+#
+# * `official` - Use the official Ghostty icon.
+# * `blueprint`, `chalkboard`, `microchip`, `glass`, `holographic`,
+# `paper`, `retro`, `xray` - Official variants of the Ghostty icon
+# hand-created by artists (no AI).
+# * `custom-style` - Use the official Ghostty icon but with custom
+# styles applied to various layers. The custom styles must be
+# specified using the additional `macos-icon`-prefixed configurations.
+# The `macos-icon-ghost-color` and `macos-icon-screen-color`
+# configurations are required for this style.
+#
+macos-icon = glass
+
+# The opacity level (opposite of transparency) of the background. A value of
+# 1 is fully opaque and a value of 0 is fully transparent. A value less than 0
+# or greater than 1 will be clamped to the nearest valid value.
+#
+# On macOS, background opacity is disabled when the terminal enters native
+# fullscreen. This is because the background becomes gray and it can cause
+# widgets to show through which isn't generally desirable.
+#
+# On macOS, changing this configuration requires restarting Ghostty completely.
+background-opacity = 0.9
+background-blur = true
+unfocused-split-opacity = 0.7
diff --git a/ghostty/config-default b/ghostty/config-default
new file mode 100644
index 0000000..6468c7e
--- /dev/null
+++ b/ghostty/config-default
@@ -0,0 +1,2522 @@
+# The font families to use.
+#
+# You can generate the list of valid values using the CLI:
+#
+# ghostty +list-fonts
+#
+# This configuration can be repeated multiple times to specify preferred
+# fallback fonts when the requested codepoint is not available in the primary
+# font. This is particularly useful for multiple languages, symbolic fonts,
+# etc.
+#
+# Notes on emoji specifically: On macOS, Ghostty by default will always use
+# Apple Color Emoji and on Linux will always use Noto Emoji. You can
+# override this behavior by specifying a font family here that contains
+# emoji glyphs.
+#
+# The specific styles (bold, italic, bold italic) do not need to be
+# explicitly set. If a style is not set, then the regular style (font-family)
+# will be searched for stylistic variants. If a stylistic variant is not
+# found, Ghostty will use the regular style. This prevents falling back to a
+# different font family just to get a style such as bold. This also applies
+# if you explicitly specify a font family for a style. For example, if you
+# set `font-family-bold = FooBar` and "FooBar" cannot be found, Ghostty will
+# use whatever font is set for `font-family` for the bold style.
+#
+# Finally, some styles may be synthesized if they are not supported.
+# For example, if a font does not have an italic style and no alternative
+# italic font is specified, Ghostty will synthesize an italic style by
+# applying a slant to the regular style. If you want to disable these
+# synthesized styles then you can use the `font-style` configurations
+# as documented below.
+#
+# You can disable styles completely by using the `font-style` set of
+# configurations. See the documentation for `font-style` for more information.
+#
+# If you want to overwrite a previous set value rather than append a fallback,
+# specify the value as `""` (empty string) to reset the list and then set the
+# new values. For example:
+#
+# font-family = ""
+# font-family = "My Favorite Font"
+#
+# Setting any of these as CLI arguments will automatically clear the
+# values set in configuration files so you don't need to specify
+# `--font-family=""` before setting a new value. You only need to specify
+# this within config files if you want to clear previously set values in
+# configuration files or on the CLI if you want to clear values set on the
+# CLI.
+#
+# Changing this configuration at runtime will only affect new terminals, i.e.
+# new windows, tabs, etc.
+font-family =
+
+font-family-bold =
+font-family-italic =
+font-family-bold-italic =
+# The named font style to use for each of the requested terminal font styles.
+# This looks up the style based on the font style string advertised by the
+# font itself. For example, "Iosevka Heavy" has a style of "Heavy".
+#
+# You can also use these fields to completely disable a font style. If you set
+# the value of the configuration below to literal `false` then that font style
+# will be disabled. If the running program in the terminal requests a disabled
+# font style, the regular font style will be used instead.
+#
+# These are only valid if its corresponding font-family is also specified. If
+# no font-family is specified, then the font-style is ignored unless you're
+# disabling the font style.
+font-style = default
+
+font-style-bold = default
+font-style-italic = default
+font-style-bold-italic = default
+# Control whether Ghostty should synthesize a style if the requested style is
+# not available in the specified font-family.
+#
+# Ghostty can synthesize bold, italic, and bold italic styles if the font
+# does not have a specific style. For bold, this is done by drawing an
+# outline around the glyph of varying thickness. For italic, this is done by
+# applying a slant to the glyph. For bold italic, both of these are applied.
+#
+# Synthetic styles are not perfect and will generally not look as good
+# as a font that has the style natively. However, they are useful to
+# provide styled text when the font does not have the style.
+#
+# Set this to "false" or "true" to disable or enable synthetic styles
+# completely. You can disable specific styles using "no-bold", "no-italic",
+# and "no-bold-italic". You can disable multiple styles by separating them
+# with a comma. For example, "no-bold,no-italic".
+#
+# Available style keys are: `bold`, `italic`, `bold-italic`.
+#
+# If synthetic styles are disabled, then the regular style will be used
+# instead if the requested style is not available. If the font has the
+# requested style, then the font will be used as-is since the style is
+# not synthetic.
+#
+# Warning: An easy mistake is to disable `bold` or `italic` but not
+# `bold-italic`. Disabling only `bold` or `italic` will NOT disable either
+# in the `bold-italic` style. If you want to disable `bold-italic`, you must
+# explicitly disable it. You cannot partially disable `bold-italic`.
+#
+# By default, synthetic styles are enabled.
+font-synthetic-style = bold,italic,bold-italic
+
+# Apply a font feature. To enable multiple font features you can repeat
+# this multiple times or use a comma-separated list of feature settings.
+#
+# The syntax for feature settings is as follows, where `feat` is a feature:
+#
+# * Enable features with e.g. `feat`, `+feat`, `feat on`, `feat=1`.
+# * Disabled features with e.g. `-feat`, `feat off`, `feat=0`.
+# * Set a feature value with e.g. `feat=2`, `feat = 3`, `feat 4`.
+# * Feature names may be wrapped in quotes, meaning this config should be
+# syntactically compatible with the `font-feature-settings` CSS property.
+#
+# The syntax is fairly loose, but invalid settings will be silently ignored.
+#
+# The font feature will apply to all fonts rendered by Ghostty. A future
+# enhancement will allow targeting specific faces.
+#
+# To disable programming ligatures, use `-calt` since this is the typical
+# feature name for programming ligatures. To look into what font features
+# your font has and what they do, use a font inspection tool such as
+# [fontdrop.info](https://fontdrop.info).
+#
+# To generally disable most ligatures, use `-calt, -liga, -dlig`.
+font-feature =
+
+# Font size in points. This value can be a non-integer and the nearest integer
+# pixel size will be selected. If you have a high dpi display where 1pt = 2px
+# then you can get an odd numbered pixel size by specifying a half point.
+#
+# For example, 13.5pt @ 2px/pt = 27px
+#
+# Changing this configuration at runtime will only affect new terminals,
+# i.e. new windows, tabs, etc. Note that you may still not see the change
+# depending on your `window-inherit-font-size` setting. If that setting is
+# true, only the first window will be affected by this change since all
+# subsequent windows will inherit the font size of the previous window.
+#
+# On Linux with GTK, font size is scaled according to both display-wide and
+# text-specific scaling factors, which are often managed by your desktop
+# environment (e.g. the GNOME display scale and large text settings).
+font-size = 13
+
+# A repeatable configuration to set one or more font variations values for
+# a variable font. A variable font is a single font, usually with a filename
+# ending in `-VF.ttf` or `-VF.otf` that contains one or more configurable axes
+# for things such as weight, slant, etc. Not all fonts support variations;
+# only fonts that explicitly state they are variable fonts will work.
+#
+# The format of this is `id=value` where `id` is the axis identifier. An axis
+# identifier is always a 4 character string, such as `wght`. To get the list
+# of supported axes, look at your font documentation or use a font inspection
+# tool.
+#
+# Invalid ids and values are usually ignored. For example, if a font only
+# supports weights from 100 to 700, setting `wght=800` will do nothing (it
+# will not be clamped to 700). You must consult your font's documentation to
+# see what values are supported.
+#
+# Common axes are: `wght` (weight), `slnt` (slant), `ital` (italic), `opsz`
+# (optical size), `wdth` (width), `GRAD` (gradient), etc.
+font-variation =
+
+font-variation-bold =
+font-variation-italic =
+font-variation-bold-italic =
+# Force one or a range of Unicode codepoints to map to a specific named font.
+# This is useful if you want to support special symbols or if you want to use
+# specific glyphs that render better for your specific font.
+#
+# The syntax is `codepoint=fontname` where `codepoint` is either a single
+# codepoint or a range. Codepoints must be specified as full Unicode
+# hex values, such as `U+ABCD`. Codepoints ranges are specified as
+# `U+ABCD-U+DEFG`. You can specify multiple ranges for the same font separated
+# by commas, such as `U+ABCD-U+DEFG,U+1234-U+5678=fontname`. The font name is
+# the same value as you would use for `font-family`.
+#
+# This configuration can be repeated multiple times to specify multiple
+# codepoint mappings.
+#
+# Changing this configuration at runtime will only affect new terminals,
+# i.e. new windows, tabs, etc.
+font-codepoint-map =
+
+# Draw fonts with a thicker stroke, if supported.
+# This is currently only supported on macOS.
+font-thicken = false
+
+# Strength of thickening when `font-thicken` is enabled.
+#
+# Valid values are integers between `0` and `255`. `0` does not correspond to
+# *no* thickening, rather it corresponds to the lightest available thickening.
+#
+# Has no effect when `font-thicken` is set to `false`.
+#
+# This is currently only supported on macOS.
+font-thicken-strength = 255
+
+# What color space to use when performing alpha blending.
+#
+# This affects the appearance of text and of any images with transparency.
+# Additionally, custom shaders will receive colors in the configured space.
+#
+# Valid values:
+#
+# * `native` - Perform alpha blending in the native color space for the OS.
+# On macOS this corresponds to Display P3, and on Linux it's sRGB.
+#
+# * `linear` - Perform alpha blending in linear space. This will eliminate
+# the darkening artifacts around the edges of text that are very visible
+# when certain color combinations are used (e.g. red / green), but makes
+# dark text look much thinner than normal and light text much thicker.
+# This is also sometimes known as "gamma correction".
+# (Currently only supported on macOS. Has no effect on Linux.)
+#
+# * `linear-corrected` - Same as `linear`, but with a correction step applied
+# for text that makes it look nearly or completely identical to `native`,
+# but without any of the darkening artifacts.
+alpha-blending = native
+
+# All of the configurations behavior adjust various metrics determined by the
+# font. The values can be integers (1, -1, etc.) or a percentage (20%, -15%,
+# etc.). In each case, the values represent the amount to change the original
+# value.
+#
+# For example, a value of `1` increases the value by 1; it does not set it to
+# literally 1. A value of `20%` increases the value by 20%. And so on.
+#
+# There is little to no validation on these values so the wrong values (e.g.
+# `-100%`) can cause the terminal to be unusable. Use with caution and reason.
+#
+# Some values are clamped to minimum or maximum values. This can make it
+# appear that certain values are ignored. For example, many `*-thickness`
+# adjustments cannot go below 1px.
+#
+# `adjust-cell-height` has some additional behaviors to describe:
+#
+# * The font will be centered vertically in the cell.
+#
+# * The cursor will remain the same size as the font, but may be
+# adjusted separately with `adjust-cursor-height`.
+#
+# * Powerline glyphs will be adjusted along with the cell height so
+# that things like status lines continue to look aligned.
+adjust-cell-width =
+
+adjust-cell-height =
+# Distance in pixels or percentage adjustment from the bottom of the cell to the text baseline.
+# Increase to move baseline UP, decrease to move baseline DOWN.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-font-baseline =
+
+# Distance in pixels or percentage adjustment from the top of the cell to the top of the underline.
+# Increase to move underline DOWN, decrease to move underline UP.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-underline-position =
+
+# Thickness in pixels of the underline.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-underline-thickness =
+
+# Distance in pixels or percentage adjustment from the top of the cell to the top of the strikethrough.
+# Increase to move strikethrough DOWN, decrease to move strikethrough UP.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-strikethrough-position =
+
+# Thickness in pixels or percentage adjustment of the strikethrough.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-strikethrough-thickness =
+
+# Distance in pixels or percentage adjustment from the top of the cell to the top of the overline.
+# Increase to move overline DOWN, decrease to move overline UP.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-overline-position =
+
+# Thickness in pixels or percentage adjustment of the overline.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-overline-thickness =
+
+# Thickness in pixels or percentage adjustment of the bar cursor and outlined rect cursor.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-cursor-thickness =
+
+# Height in pixels or percentage adjustment of the cursor. Currently applies to all cursor types:
+# bar, rect, and outlined rect.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-cursor-height =
+
+# Thickness in pixels or percentage adjustment of box drawing characters.
+# See the notes about adjustments in `adjust-cell-width`.
+adjust-box-thickness =
+
+# The method to use for calculating the cell width of a grapheme cluster.
+# The default value is `unicode` which uses the Unicode standard to determine
+# grapheme width. This results in correct grapheme width but may result in
+# cursor-desync issues with some programs (such as shells) that may use a
+# legacy method such as `wcswidth`.
+#
+# Valid values are:
+#
+# * `legacy` - Use a legacy method to determine grapheme width, such as
+# wcswidth This maximizes compatibility with legacy programs but may result
+# in incorrect grapheme width for certain graphemes such as skin-tone
+# emoji, non-English characters, etc.
+#
+# This is called "legacy" and not something more specific because the
+# behavior is undefined and we want to retain the ability to modify it.
+# For example, we may or may not use libc `wcswidth` now or in the future.
+#
+# * `unicode` - Use the Unicode standard to determine grapheme width.
+#
+# If a running program explicitly enables terminal mode 2027, then `unicode`
+# width will be forced regardless of this configuration. When mode 2027 is
+# reset, this configuration will be used again.
+#
+# This configuration can be changed at runtime but will not affect existing
+# terminals. Only new terminals will use the new configuration.
+grapheme-width-method = unicode
+
+# FreeType load flags to enable. The format of this is a list of flags to
+# enable separated by commas. If you prefix a flag with `no-` then it is
+# disabled. If you omit a flag, its default value is used, so you must
+# explicitly disable flags you don't want. You can also use `true` or `false`
+# to turn all flags on or off.
+#
+# This configuration only applies to Ghostty builds that use FreeType.
+# This is usually the case only for Linux builds. macOS uses CoreText
+# and does not have an equivalent configuration.
+#
+# Available flags:
+#
+# * `hinting` - Enable or disable hinting, enabled by default.
+# * `force-autohint` - Use the freetype auto-hinter rather than the
+# font's native hinter. Enabled by default.
+# * `monochrome` - Instructs renderer to use 1-bit monochrome
+# rendering. This option doesn't impact the hinter.
+# Enabled by default.
+# * `autohint` - Use the freetype auto-hinter. Enabled by default.
+#
+# Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint`
+freetype-load-flags = hinting,force-autohint,monochrome,autohint
+
+# A theme to use. This can be a built-in theme name, a custom theme
+# name, or an absolute path to a custom theme file. Ghostty also supports
+# specifying a different theme to use for light and dark mode. Each
+# option is documented below.
+#
+# If the theme is an absolute pathname, Ghostty will attempt to load that
+# file as a theme. If that file does not exist or is inaccessible, an error
+# will be logged and no other directories will be searched.
+#
+# If the theme is not an absolute pathname, two different directories will be
+# searched for a file name that matches the theme. This is case sensitive on
+# systems with case-sensitive filesystems. It is an error for a theme name to
+# include path separators unless it is an absolute pathname.
+#
+# The first directory is the `themes` subdirectory of your Ghostty
+# configuration directory. This is `$XDG_CONFIG_DIR/ghostty/themes` or
+# `~/.config/ghostty/themes`.
+#
+# The second directory is the `themes` subdirectory of the Ghostty resources
+# directory. Ghostty ships with a multitude of themes that will be installed
+# into this directory. On macOS, this list is in the
+# `Ghostty.app/Contents/Resources/ghostty/themes` directory. On Linux, this
+# list is in the `share/ghostty/themes` directory (wherever you installed the
+# Ghostty "share" directory.
+#
+# To see a list of available themes, run `ghostty +list-themes`.
+#
+# A theme file is simply another Ghostty configuration file. They share
+# the same syntax and same configuration options. A theme can set any valid
+# configuration option so please do not use a theme file from an untrusted
+# source. The built-in themes are audited to only set safe configuration
+# options.
+#
+# Some options cannot be set within theme files. The reason these are not
+# supported should be self-evident. A theme file cannot set `theme` or
+# `config-file`. At the time of writing this, Ghostty will not show any
+# warnings or errors if you set these options in a theme file but they will
+# be silently ignored.
+#
+# Any additional colors specified via background, foreground, palette, etc.
+# will override the colors specified in the theme.
+#
+# To specify a different theme for light and dark mode, use the following
+# syntax: `light:theme-name,dark:theme-name`. For example:
+# `light:rose-pine-dawn,dark:rose-pine`. Whitespace around all values are
+# trimmed and order of light and dark does not matter. Both light and dark
+# must be specified in this form. In this form, the theme used will be
+# based on the current desktop environment theme.
+#
+# There are some known bugs with light/dark mode theming. These will
+# be fixed in a future update:
+#
+# - macOS: titlebar tabs style is not updated when switching themes.
+#
+theme =
+
+# Background color for the window.
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+background = #282c34
+
+# Foreground color for the window.
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+foreground = #ffffff
+
+# The foreground and background color for selection. If this is not set, then
+# the selection color is just the inverted window background and foreground
+# (note: not to be confused with the cell bg/fg).
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+selection-foreground =
+
+selection-background =
+# Swap the foreground and background colors of cells for selection. This
+# option overrides the `selection-foreground` and `selection-background`
+# options.
+#
+# If you select across cells with differing foregrounds and backgrounds, the
+# selection color will vary across the selection.
+selection-invert-fg-bg = false
+
+# The minimum contrast ratio between the foreground and background colors.
+# The contrast ratio is a value between 1 and 21. A value of 1 allows for no
+# contrast (e.g. black on black). This value is the contrast ratio as defined
+# by the [WCAG 2.0 specification](https://www.w3.org/TR/WCAG20/).
+#
+# If you want to avoid invisible text (same color as background), a value of
+# 1.1 is a good value. If you want to avoid text that is difficult to read, a
+# value of 3 or higher is a good value. The higher the value, the more likely
+# that text will become black or white.
+#
+# This value does not apply to Emoji or images.
+minimum-contrast = 1
+
+# Color palette for the 256 color form that many terminal applications use.
+# The syntax of this configuration is `N=COLOR` where `N` is 0 to 255 (for
+# the 256 colors in the terminal color table) and `COLOR` is a typical RGB
+# color code such as `#AABBCC` or `AABBCC`, or a named X11 color.
+#
+# The palette index can be in decimal, binary, octal, or hexadecimal.
+# Decimal is assumed unless a prefix is used: `0b` for binary, `0o` for octal,
+# and `0x` for hexadecimal.
+#
+# For definitions on the color indices and what they canonically map to,
+# [see this cheat sheet](https://www.ditig.com/256-colors-cheat-sheet).
+palette = 0=#1d1f21
+palette = 1=#cc6666
+palette = 2=#b5bd68
+palette = 3=#f0c674
+palette = 4=#81a2be
+palette = 5=#b294bb
+palette = 6=#8abeb7
+palette = 7=#c5c8c6
+palette = 8=#666666
+palette = 9=#d54e53
+palette = 10=#b9ca4a
+palette = 11=#e7c547
+palette = 12=#7aa6da
+palette = 13=#c397d8
+palette = 14=#70c0b1
+palette = 15=#eaeaea
+palette = 16=#000000
+palette = 17=#00005f
+palette = 18=#000087
+palette = 19=#0000af
+palette = 20=#0000d7
+palette = 21=#0000ff
+palette = 22=#005f00
+palette = 23=#005f5f
+palette = 24=#005f87
+palette = 25=#005faf
+palette = 26=#005fd7
+palette = 27=#005fff
+palette = 28=#008700
+palette = 29=#00875f
+palette = 30=#008787
+palette = 31=#0087af
+palette = 32=#0087d7
+palette = 33=#0087ff
+palette = 34=#00af00
+palette = 35=#00af5f
+palette = 36=#00af87
+palette = 37=#00afaf
+palette = 38=#00afd7
+palette = 39=#00afff
+palette = 40=#00d700
+palette = 41=#00d75f
+palette = 42=#00d787
+palette = 43=#00d7af
+palette = 44=#00d7d7
+palette = 45=#00d7ff
+palette = 46=#00ff00
+palette = 47=#00ff5f
+palette = 48=#00ff87
+palette = 49=#00ffaf
+palette = 50=#00ffd7
+palette = 51=#00ffff
+palette = 52=#5f0000
+palette = 53=#5f005f
+palette = 54=#5f0087
+palette = 55=#5f00af
+palette = 56=#5f00d7
+palette = 57=#5f00ff
+palette = 58=#5f5f00
+palette = 59=#5f5f5f
+palette = 60=#5f5f87
+palette = 61=#5f5faf
+palette = 62=#5f5fd7
+palette = 63=#5f5fff
+palette = 64=#5f8700
+palette = 65=#5f875f
+palette = 66=#5f8787
+palette = 67=#5f87af
+palette = 68=#5f87d7
+palette = 69=#5f87ff
+palette = 70=#5faf00
+palette = 71=#5faf5f
+palette = 72=#5faf87
+palette = 73=#5fafaf
+palette = 74=#5fafd7
+palette = 75=#5fafff
+palette = 76=#5fd700
+palette = 77=#5fd75f
+palette = 78=#5fd787
+palette = 79=#5fd7af
+palette = 80=#5fd7d7
+palette = 81=#5fd7ff
+palette = 82=#5fff00
+palette = 83=#5fff5f
+palette = 84=#5fff87
+palette = 85=#5fffaf
+palette = 86=#5fffd7
+palette = 87=#5fffff
+palette = 88=#870000
+palette = 89=#87005f
+palette = 90=#870087
+palette = 91=#8700af
+palette = 92=#8700d7
+palette = 93=#8700ff
+palette = 94=#875f00
+palette = 95=#875f5f
+palette = 96=#875f87
+palette = 97=#875faf
+palette = 98=#875fd7
+palette = 99=#875fff
+palette = 100=#878700
+palette = 101=#87875f
+palette = 102=#878787
+palette = 103=#8787af
+palette = 104=#8787d7
+palette = 105=#8787ff
+palette = 106=#87af00
+palette = 107=#87af5f
+palette = 108=#87af87
+palette = 109=#87afaf
+palette = 110=#87afd7
+palette = 111=#87afff
+palette = 112=#87d700
+palette = 113=#87d75f
+palette = 114=#87d787
+palette = 115=#87d7af
+palette = 116=#87d7d7
+palette = 117=#87d7ff
+palette = 118=#87ff00
+palette = 119=#87ff5f
+palette = 120=#87ff87
+palette = 121=#87ffaf
+palette = 122=#87ffd7
+palette = 123=#87ffff
+palette = 124=#af0000
+palette = 125=#af005f
+palette = 126=#af0087
+palette = 127=#af00af
+palette = 128=#af00d7
+palette = 129=#af00ff
+palette = 130=#af5f00
+palette = 131=#af5f5f
+palette = 132=#af5f87
+palette = 133=#af5faf
+palette = 134=#af5fd7
+palette = 135=#af5fff
+palette = 136=#af8700
+palette = 137=#af875f
+palette = 138=#af8787
+palette = 139=#af87af
+palette = 140=#af87d7
+palette = 141=#af87ff
+palette = 142=#afaf00
+palette = 143=#afaf5f
+palette = 144=#afaf87
+palette = 145=#afafaf
+palette = 146=#afafd7
+palette = 147=#afafff
+palette = 148=#afd700
+palette = 149=#afd75f
+palette = 150=#afd787
+palette = 151=#afd7af
+palette = 152=#afd7d7
+palette = 153=#afd7ff
+palette = 154=#afff00
+palette = 155=#afff5f
+palette = 156=#afff87
+palette = 157=#afffaf
+palette = 158=#afffd7
+palette = 159=#afffff
+palette = 160=#d70000
+palette = 161=#d7005f
+palette = 162=#d70087
+palette = 163=#d700af
+palette = 164=#d700d7
+palette = 165=#d700ff
+palette = 166=#d75f00
+palette = 167=#d75f5f
+palette = 168=#d75f87
+palette = 169=#d75faf
+palette = 170=#d75fd7
+palette = 171=#d75fff
+palette = 172=#d78700
+palette = 173=#d7875f
+palette = 174=#d78787
+palette = 175=#d787af
+palette = 176=#d787d7
+palette = 177=#d787ff
+palette = 178=#d7af00
+palette = 179=#d7af5f
+palette = 180=#d7af87
+palette = 181=#d7afaf
+palette = 182=#d7afd7
+palette = 183=#d7afff
+palette = 184=#d7d700
+palette = 185=#d7d75f
+palette = 186=#d7d787
+palette = 187=#d7d7af
+palette = 188=#d7d7d7
+palette = 189=#d7d7ff
+palette = 190=#d7ff00
+palette = 191=#d7ff5f
+palette = 192=#d7ff87
+palette = 193=#d7ffaf
+palette = 194=#d7ffd7
+palette = 195=#d7ffff
+palette = 196=#ff0000
+palette = 197=#ff005f
+palette = 198=#ff0087
+palette = 199=#ff00af
+palette = 200=#ff00d7
+palette = 201=#ff00ff
+palette = 202=#ff5f00
+palette = 203=#ff5f5f
+palette = 204=#ff5f87
+palette = 205=#ff5faf
+palette = 206=#ff5fd7
+palette = 207=#ff5fff
+palette = 208=#ff8700
+palette = 209=#ff875f
+palette = 210=#ff8787
+palette = 211=#ff87af
+palette = 212=#ff87d7
+palette = 213=#ff87ff
+palette = 214=#ffaf00
+palette = 215=#ffaf5f
+palette = 216=#ffaf87
+palette = 217=#ffafaf
+palette = 218=#ffafd7
+palette = 219=#ffafff
+palette = 220=#ffd700
+palette = 221=#ffd75f
+palette = 222=#ffd787
+palette = 223=#ffd7af
+palette = 224=#ffd7d7
+palette = 225=#ffd7ff
+palette = 226=#ffff00
+palette = 227=#ffff5f
+palette = 228=#ffff87
+palette = 229=#ffffaf
+palette = 230=#ffffd7
+palette = 231=#ffffff
+palette = 232=#080808
+palette = 233=#121212
+palette = 234=#1c1c1c
+palette = 235=#262626
+palette = 236=#303030
+palette = 237=#3a3a3a
+palette = 238=#444444
+palette = 239=#4e4e4e
+palette = 240=#585858
+palette = 241=#626262
+palette = 242=#6c6c6c
+palette = 243=#767676
+palette = 244=#808080
+palette = 245=#8a8a8a
+palette = 246=#949494
+palette = 247=#9e9e9e
+palette = 248=#a8a8a8
+palette = 249=#b2b2b2
+palette = 250=#bcbcbc
+palette = 251=#c6c6c6
+palette = 252=#d0d0d0
+palette = 253=#dadada
+palette = 254=#e4e4e4
+palette = 255=#eeeeee
+
+# The color of the cursor. If this is not set, a default will be chosen.
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+cursor-color =
+
+# Swap the foreground and background colors of the cell under the cursor. This
+# option overrides the `cursor-color` and `cursor-text` options.
+cursor-invert-fg-bg = false
+
+# The opacity level (opposite of transparency) of the cursor. A value of 1
+# is fully opaque and a value of 0 is fully transparent. A value less than 0
+# or greater than 1 will be clamped to the nearest valid value. Note that a
+# sufficiently small value such as 0.3 may be effectively invisible and may
+# make it difficult to find the cursor.
+cursor-opacity = 1
+
+# The style of the cursor. This sets the default style. A running program can
+# still request an explicit cursor style using escape sequences (such as `CSI
+# q`). Shell configurations will often request specific cursor styles.
+#
+# Note that shell integration will automatically set the cursor to a bar at
+# a prompt, regardless of this configuration. You can disable that behavior
+# by specifying `shell-integration-features = no-cursor` or disabling shell
+# integration entirely.
+#
+# Valid values are:
+#
+# * `block`
+# * `bar`
+# * `underline`
+# * `block_hollow`
+#
+cursor-style = block
+
+# Sets the default blinking state of the cursor. This is just the default
+# state; running programs may override the cursor style using `DECSCUSR` (`CSI
+# q`).
+#
+# If this is not set, the cursor blinks by default. Note that this is not the
+# same as a "true" value, as noted below.
+#
+# If this is not set at all (`null`), then Ghostty will respect DEC Mode 12
+# (AT&T cursor blink) as an alternate approach to turning blinking on/off. If
+# this is set to any value other than null, DEC mode 12 will be ignored but
+# `DECSCUSR` will still be respected.
+#
+# Valid values are:
+#
+# * ` ` (blank)
+# * `true`
+# * `false`
+#
+cursor-style-blink =
+
+# The color of the text under the cursor. If this is not set, a default will
+# be chosen.
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+cursor-text =
+
+# Enables the ability to move the cursor at prompts by using `alt+click` on
+# Linux and `option+click` on macOS.
+#
+# This feature requires shell integration (specifically prompt marking
+# via `OSC 133`) and only works in primary screen mode. Alternate screen
+# applications like vim usually have their own version of this feature but
+# this configuration doesn't control that.
+#
+# It should be noted that this feature works by translating your desired
+# position into a series of synthetic arrow key movements, so some weird
+# behavior around edge cases are to be expected. This is unfortunately how
+# this feature is implemented across terminals because there isn't any other
+# way to implement it.
+cursor-click-to-move = true
+
+# Hide the mouse immediately when typing. The mouse becomes visible again
+# when the mouse is used (button, movement, etc.). Platform-specific behavior
+# may dictate other scenarios where the mouse is shown. For example on macOS,
+# the mouse is shown again when a new window, tab, or split is created.
+mouse-hide-while-typing = false
+
+# Determines whether running programs can detect the shift key pressed with a
+# mouse click. Typically, the shift key is used to extend mouse selection.
+#
+# The default value of `false` means that the shift key is not sent with
+# the mouse protocol and will extend the selection. This value can be
+# conditionally overridden by the running program with the `XTSHIFTESCAPE`
+# sequence.
+#
+# The value `true` means that the shift key is sent with the mouse protocol
+# but the running program can override this behavior with `XTSHIFTESCAPE`.
+#
+# The value `never` is the same as `false` but the running program cannot
+# override this behavior with `XTSHIFTESCAPE`. The value `always` is the
+# same as `true` but the running program cannot override this behavior with
+# `XTSHIFTESCAPE`.
+#
+# If you always want shift to extend mouse selection even if the program
+# requests otherwise, set this to `never`.
+#
+# Valid values are:
+#
+# * `true`
+# * `false`
+# * `always`
+# * `never`
+#
+mouse-shift-capture = false
+
+# Multiplier for scrolling distance with the mouse wheel. Any value less
+# than 0.01 or greater than 10,000 will be clamped to the nearest valid
+# value.
+#
+# A value of "1" (default) scrolls the default amount. A value of "2" scrolls
+# double the default amount. A value of "0.5" scrolls half the default amount.
+# Et cetera.
+mouse-scroll-multiplier = 1
+
+# The opacity level (opposite of transparency) of the background. A value of
+# 1 is fully opaque and a value of 0 is fully transparent. A value less than 0
+# or greater than 1 will be clamped to the nearest valid value.
+#
+# On macOS, background opacity is disabled when the terminal enters native
+# fullscreen. This is because the background becomes gray and it can cause
+# widgets to show through which isn't generally desirable.
+#
+# On macOS, changing this configuration requires restarting Ghostty completely.
+background-opacity = 1
+
+# Whether to blur the background when `background-opacity` is less than 1.
+#
+# Valid values are:
+#
+# * a nonnegative integer specifying the *blur intensity*
+# * `false`, equivalent to a blur intensity of 0
+# * `true`, equivalent to the default blur intensity of 20, which is
+# reasonable for a good looking blur. Higher blur intensities may
+# cause strange rendering and performance issues.
+#
+# Supported on macOS and on some Linux desktop environments, including:
+#
+# * KDE Plasma (Wayland and X11)
+#
+# Warning: the exact blur intensity is _ignored_ under KDE Plasma, and setting
+# this setting to either `true` or any positive blur intensity value would
+# achieve the same effect. The reason is that KWin, the window compositor
+# powering Plasma, only has one global blur setting and does not allow
+# applications to specify individual blur settings.
+#
+# To configure KWin's global blur setting, open System Settings and go to
+# "Apps & Windows" > "Window Management" > "Desktop Effects" and select the
+# "Blur" plugin. If disabled, enable it by ticking the checkbox to the left.
+# Then click on the "Configure" button and there will be two sliders that
+# allow you to set background blur and noise intensities for all apps,
+# including Ghostty.
+#
+# All other Linux desktop environments are as of now unsupported. Users may
+# need to set environment-specific settings and/or install third-party plugins
+# in order to support background blur, as there isn't a unified interface for
+# doing so.
+background-blur = false
+
+# The opacity level (opposite of transparency) of an unfocused split.
+# Unfocused splits by default are slightly faded out to make it easier to see
+# which split is focused. To disable this feature, set this value to 1.
+#
+# A value of 1 is fully opaque and a value of 0 is fully transparent. Because
+# "0" is not useful (it makes the window look very weird), the minimum value
+# is 0.15. This value still looks weird but you can at least see what's going
+# on. A value outside of the range 0.15 to 1 will be clamped to the nearest
+# valid value.
+unfocused-split-opacity = 0.7
+
+# The color to dim the unfocused split. Unfocused splits are dimmed by
+# rendering a semi-transparent rectangle over the split. This sets the color of
+# that rectangle and can be used to carefully control the dimming effect.
+#
+# This will default to the background color.
+#
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+unfocused-split-fill =
+
+# The color of the split divider. If this is not set, a default will be chosen.
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+split-divider-color =
+
+# The command to run, usually a shell. If this is not an absolute path, it'll
+# be looked up in the `PATH`. If this is not set, a default will be looked up
+# from your system. The rules for the default lookup are:
+#
+# * `SHELL` environment variable
+#
+# * `passwd` entry (user information)
+#
+# This can contain additional arguments to run the command with. If additional
+# arguments are provided, the command will be executed using `/bin/sh -c`.
+# Ghostty does not do any shell command parsing.
+#
+# This command will be used for all new terminal surfaces, i.e. new windows,
+# tabs, etc. If you want to run a command only for the first terminal surface
+# created when Ghostty starts, use the `initial-command` configuration.
+#
+# Ghostty supports the common `-e` flag for executing a command with
+# arguments. For example, `ghostty -e fish --with --custom --args`.
+# This flag sets the `initial-command` configuration, see that for more
+# information.
+command =
+
+# This is the same as "command", but only applies to the first terminal
+# surface created when Ghostty starts. Subsequent terminal surfaces will use
+# the `command` configuration.
+#
+# After the first terminal surface is created (or closed), there is no
+# way to run this initial command again automatically. As such, setting
+# this at runtime works but will only affect the next terminal surface
+# if it is the first one ever created.
+#
+# If you're using the `ghostty` CLI there is also a shortcut to set this
+# with arguments directly: you can use the `-e` flag. For example: `ghostty -e
+# fish --with --custom --args`. The `-e` flag automatically forces some
+# other behaviors as well:
+#
+# * `gtk-single-instance=false` - This ensures that a new instance is
+# launched and the CLI args are respected.
+#
+# * `quit-after-last-window-closed=true` - This ensures that the Ghostty
+# process will exit when the command exits. Additionally, the
+# `quit-after-last-window-closed-delay` is unset.
+#
+# * `shell-integration=detect` (if not `none`) - This prevents forcibly
+# injecting any configured shell integration into the command's
+# environment. With `-e` its highly unlikely that you're executing a
+# shell and forced shell integration is likely to cause problems
+# (e.g. by wrapping your command in a shell, setting env vars, etc.).
+# This is a safety measure to prevent unexpected behavior. If you want
+# shell integration with a `-e`-executed command, you must either
+# name your binary appropriately or source the shell integration script
+# manually.
+#
+initial-command =
+
+# If true, keep the terminal open after the command exits. Normally, the
+# terminal window closes when the running command (such as a shell) exits.
+# With this true, the terminal window will stay open until any keypress is
+# received.
+#
+# This is primarily useful for scripts or debugging.
+wait-after-command = false
+
+# The number of milliseconds of runtime below which we consider a process exit
+# to be abnormal. This is used to show an error message when the process exits
+# too quickly.
+#
+# On Linux, this must be paired with a non-zero exit code. On macOS, we allow
+# any exit code because of the way shell processes are launched via the login
+# command.
+abnormal-command-exit-runtime = 250
+
+# The size of the scrollback buffer in bytes. This also includes the active
+# screen. No matter what this is set to, enough memory will always be
+# allocated for the visible screen and anything leftover is the limit for
+# the scrollback.
+#
+# When this limit is reached, the oldest lines are removed from the
+# scrollback.
+#
+# Scrollback currently exists completely in memory. This means that the
+# larger this value, the larger potential memory usage. Scrollback is
+# allocated lazily up to this limit, so if you set this to a very large
+# value, it will not immediately consume a lot of memory.
+#
+# This size is per terminal surface, not for the entire application.
+#
+# It is not currently possible to set an unlimited scrollback buffer.
+# This is a future planned feature.
+#
+# This can be changed at runtime but will only affect new terminal surfaces.
+scrollback-limit = 10000000
+
+# Match a regular expression against the terminal text and associate clicking
+# it with an action. This can be used to match URLs, file paths, etc. Actions
+# can be opening using the system opener (e.g. `open` or `xdg-open`) or
+# executing any arbitrary binding action.
+#
+# Links that are configured earlier take precedence over links that are
+# configured later.
+#
+# A default link that matches a URL and opens it in the system opener always
+# exists. This can be disabled using `link-url`.
+#
+# TODO: This can't currently be set!
+
+# Enable URL matching. URLs are matched on hover with control (Linux) or
+# super (macOS) pressed and open using the default system application for
+# the linked URL.
+#
+# The URL matcher is always lowest priority of any configured links (see
+# `link`). If you want to customize URL matching, use `link` and disable this.
+link-url = true
+
+# Whether to start the window in a maximized state. This setting applies
+# to new windows and does not apply to tabs, splits, etc. However, this setting
+# will apply to all new windows, not just the first one.
+maximize = false
+
+# Start new windows in fullscreen. This setting applies to new windows and
+# does not apply to tabs, splits, etc. However, this setting will apply to all
+# new windows, not just the first one.
+#
+# On macOS, this setting does not work if window-decoration is set to
+# "false", because native fullscreen on macOS requires window decorations
+# to be set.
+fullscreen = false
+
+# The title Ghostty will use for the window. This will force the title of the
+# window to be this title at all times and Ghostty will ignore any set title
+# escape sequences programs (such as Neovim) may send.
+#
+# If you want a blank title, set this to one or more spaces by quoting
+# the value. For example, `title = " "`. This effectively hides the title.
+# This is necessary because setting a blank value resets the title to the
+# default value of the running program.
+#
+# This configuration can be reloaded at runtime. If it is set, the title
+# will update for all windows. If it is unset, the next title change escape
+# sequence will be honored but previous changes will not retroactively
+# be set. This latter case may require you to restart programs such as Neovim
+# to get the new title.
+title =
+
+# The setting that will change the application class value.
+#
+# This controls the class field of the `WM_CLASS` X11 property (when running
+# under X11), and the Wayland application ID (when running under Wayland).
+#
+# Note that changing this value between invocations will create new, separate
+# instances, of Ghostty when running with `gtk-single-instance=true`. See that
+# option for more details.
+#
+# The class name must follow the requirements defined [in the GTK
+# documentation](https://docs.gtk.org/gio/type_func.Application.id_is_valid.html).
+#
+# The default is `com.mitchellh.ghostty`.
+#
+# This only affects GTK builds.
+class =
+
+# This controls the instance name field of the `WM_CLASS` X11 property when
+# running under X11. It has no effect otherwise.
+#
+# The default is `ghostty`.
+#
+# This only affects GTK builds.
+x11-instance-name =
+
+# The directory to change to after starting the command.
+#
+# This setting is secondary to the `window-inherit-working-directory`
+# setting. If a previous Ghostty terminal exists in the same process,
+# `window-inherit-working-directory` will take precedence. Otherwise, this
+# setting will be used. Typically, this setting is used only for the first
+# window.
+#
+# The default is `inherit` except in special scenarios listed next. On macOS,
+# if Ghostty can detect it is launched from launchd (double-clicked) or
+# `open`, then it defaults to `home`. On Linux with GTK, if Ghostty can detect
+# it was launched from a desktop launcher, then it defaults to `home`.
+#
+# The value of this must be an absolute value or one of the special values
+# below:
+#
+# * `home` - The home directory of the executing user.
+#
+# * `inherit` - The working directory of the launching process.
+working-directory =
+
+# Key bindings. The format is `trigger=action`. Duplicate triggers will
+# overwrite previously set values. The list of actions is available in
+# the documentation or using the `ghostty +list-actions` command.
+#
+# Trigger: `+`-separated list of keys and modifiers. Example: `ctrl+a`,
+# `ctrl+shift+b`, `up`.
+#
+# Valid keys are currently only listed in the
+# [Ghostty source code](https://github.com/ghostty-org/ghostty/blob/d6e76858164d52cff460fedc61ddf2e560912d71/src/input/key.zig#L255).
+# This is a documentation limitation and we will improve this in the future.
+# A common gotcha is that numeric keys are written as words: e.g. `one`,
+# `two`, `three`, etc. and not `1`, `2`, `3`. This will also be improved in
+# the future.
+#
+# Valid modifiers are `shift`, `ctrl` (alias: `control`), `alt` (alias: `opt`,
+# `option`), and `super` (alias: `cmd`, `command`). You may use the modifier
+# or the alias. When debugging keybinds, the non-aliased modifier will always
+# be used in output.
+#
+# Note: The fn or "globe" key on keyboards are not supported as a
+# modifier. This is a limitation of the operating systems and GUI toolkits
+# that Ghostty uses.
+#
+# Some additional notes for triggers:
+#
+# * modifiers cannot repeat, `ctrl+ctrl+a` is invalid.
+#
+# * modifiers and keys can be in any order, `shift+a+ctrl` is *weird*,
+# but valid.
+#
+# * only a single key input is allowed, `ctrl+a+b` is invalid.
+#
+# * the key input can be prefixed with `physical:` to specify a
+# physical key mapping rather than a logical one. A physical key
+# mapping responds to the hardware keycode and not the keycode
+# translated by any system keyboard layouts. Example: "ctrl+physical:a"
+#
+# You may also specify multiple triggers separated by `>` to require a
+# sequence of triggers to activate the action. For example,
+# `ctrl+a>n=new_window` will only trigger the `new_window` action if the
+# user presses `ctrl+a` followed separately by `n`. In other software, this
+# is sometimes called a leader key, a key chord, a key table, etc. There
+# is no hardcoded limit on the number of parts in a sequence.
+#
+# Warning: If you define a sequence as a CLI argument to `ghostty`,
+# you probably have to quote the keybind since `>` is a special character
+# in most shells. Example: ghostty --keybind='ctrl+a>n=new_window'
+#
+# A trigger sequence has some special handling:
+#
+# * Ghostty will wait an indefinite amount of time for the next key in
+# the sequence. There is no way to specify a timeout. The only way to
+# force the output of a prefix key is to assign another keybind to
+# specifically output that key (e.g. `ctrl+a>ctrl+a=text:foo`) or
+# press an unbound key which will send both keys to the program.
+#
+# * If a prefix in a sequence is previously bound, the sequence will
+# override the previous binding. For example, if `ctrl+a` is bound to
+# `new_window` and `ctrl+a>n` is bound to `new_tab`, pressing `ctrl+a`
+# will do nothing.
+#
+# * Adding to the above, if a previously bound sequence prefix is
+# used in a new, non-sequence binding, the entire previously bound
+# sequence will be unbound. For example, if you bind `ctrl+a>n` and
+# `ctrl+a>t`, and then bind `ctrl+a` directly, both `ctrl+a>n` and
+# `ctrl+a>t` will become unbound.
+#
+# * Trigger sequences are not allowed for `global:` or `all:`-prefixed
+# triggers. This is a limitation we could remove in the future.
+#
+# Action is the action to take when the trigger is satisfied. It takes the
+# format `action` or `action:param`. The latter form is only valid if the
+# action requires a parameter.
+#
+# * `ignore` - Do nothing, ignore the key input. This can be used to
+# black hole certain inputs to have no effect.
+#
+# * `unbind` - Remove the binding. This makes it so the previous action
+# is removed, and the key will be sent through to the child command
+# if it is printable. Unbind will remove any matching trigger,
+# including `physical:`-prefixed triggers without specifying the
+# prefix.
+#
+# * `csi:text` - Send a CSI sequence. e.g. `csi:A` sends "cursor up".
+#
+# * `esc:text` - Send an escape sequence. e.g. `esc:d` deletes to the
+# end of the word to the right.
+#
+# * `text:text` - Send a string. Uses Zig string literal syntax.
+# e.g. `text:\x15` sends Ctrl-U.
+#
+# * All other actions can be found in the documentation or by using the
+# `ghostty +list-actions` command.
+#
+# Some notes for the action:
+#
+# * The parameter is taken as-is after the `:`. Double quotes or
+# other mechanisms are included and NOT parsed. If you want to
+# send a string value that includes spaces, wrap the entire
+# trigger/action in double quotes. Example: `--keybind="up=csi:A B"`
+#
+# There are some additional special values that can be specified for
+# keybind:
+#
+# * `keybind=clear` will clear all set keybindings. Warning: this
+# removes ALL keybindings up to this point, including the default
+# keybindings.
+#
+# The keybind trigger can be prefixed with some special values to change
+# the behavior of the keybind. These are:
+#
+# * `all:` - Make the keybind apply to all terminal surfaces. By default,
+# keybinds only apply to the focused terminal surface. If this is true,
+# then the keybind will be sent to all terminal surfaces. This only
+# applies to actions that are surface-specific. For actions that
+# are already global (e.g. `quit`), this prefix has no effect.
+#
+# * `global:` - Make the keybind global. By default, keybinds only work
+# within Ghostty and under the right conditions (application focused,
+# sometimes terminal focused, etc.). If you want a keybind to work
+# globally across your system (e.g. even when Ghostty is not focused),
+# specify this prefix. This prefix implies `all:`. Note: this does not
+# work in all environments; see the additional notes below for more
+# information.
+#
+# * `unconsumed:` - Do not consume the input. By default, a keybind
+# will consume the input, meaning that the associated encoding (if
+# any) will not be sent to the running program in the terminal. If
+# you wish to send the encoded value to the program, specify the
+# `unconsumed:` prefix before the entire keybind. For example:
+# `unconsumed:ctrl+a=reload_config`. `global:` and `all:`-prefixed
+# keybinds will always consume the input regardless of this setting.
+# Since they are not associated with a specific terminal surface,
+# they're never encoded.
+#
+# * `performable:` - Only consume the input if the action is able to be
+# performed. For example, the `copy_to_clipboard` action will only
+# consume the input if there is a selection to copy. If there is no
+# selection, Ghostty behaves as if the keybind was not set. This has
+# no effect with `global:` or `all:`-prefixed keybinds. For key
+# sequences, this will reset the sequence if the action is not
+# performable (acting identically to not having a keybind set at
+# all).
+#
+# Performable keybinds will not appear as menu shortcuts in the
+# application menu. This is because the menu shortcuts force the
+# action to be performed regardless of the state of the terminal.
+# Performable keybinds will still work, they just won't appear as
+# a shortcut label in the menu.
+#
+# Keybind triggers are not unique per prefix combination. For example,
+# `ctrl+a` and `global:ctrl+a` are not two separate keybinds. The keybind
+# set later will overwrite the keybind set earlier. In this case, the
+# `global:` keybind will be used.
+#
+# Multiple prefixes can be specified. For example,
+# `global:unconsumed:ctrl+a=reload_config` will make the keybind global
+# and not consume the input to reload the config.
+#
+# Note: `global:` is only supported on macOS. On macOS,
+# this feature requires accessibility permissions to be granted to Ghostty.
+# When a `global:` keybind is specified and Ghostty is launched or reloaded,
+# Ghostty will attempt to request these permissions. If the permissions are
+# not granted, the keybind will not work. On macOS, you can find these
+# permissions in System Preferences -> Privacy & Security -> Accessibility.
+keybind = super+page_up=scroll_page_up
+keybind = super+ctrl+equal=equalize_splits
+keybind = super+physical:four=goto_tab:4
+keybind = super+shift+down=jump_to_prompt:1
+keybind = super+shift+w=close_window
+keybind = super+shift+left_bracket=previous_tab
+keybind = super+backspace=text:\x15
+keybind = super+alt+w=close_tab
+keybind = super+w=close_surface
+keybind = super+alt+i=inspector:toggle
+keybind = super+physical:eight=goto_tab:8
+keybind = super+alt+right=goto_split:right
+keybind = shift+up=adjust_selection:up
+keybind = super+down=jump_to_prompt:1
+keybind = super+enter=toggle_fullscreen
+keybind = super+t=new_tab
+keybind = super+c=copy_to_clipboard
+keybind = super+shift+right_bracket=next_tab
+keybind = super+physical:one=goto_tab:1
+keybind = shift+left=adjust_selection:left
+keybind = super+equal=increase_font_size:1
+keybind = shift+page_up=adjust_selection:page_up
+keybind = super+physical:three=goto_tab:3
+keybind = super+right=text:\x05
+keybind = super+d=new_split:right
+keybind = super+ctrl+down=resize_split:down,10
+keybind = shift+end=adjust_selection:end
+keybind = super+plus=increase_font_size:1
+keybind = super+q=quit
+keybind = super+home=scroll_to_top
+keybind = super+ctrl+left=resize_split:left,10
+keybind = alt+left=esc:b
+keybind = super+ctrl+up=resize_split:up,10
+keybind = super+left=text:\x01
+keybind = super+shift+up=jump_to_prompt:-1
+keybind = shift+right=adjust_selection:right
+keybind = super+comma=open_config
+keybind = super+shift+comma=reload_config
+keybind = super+minus=decrease_font_size:1
+keybind = shift+page_down=adjust_selection:page_down
+keybind = ctrl+tab=next_tab
+keybind = super+a=select_all
+keybind = alt+right=esc:f
+keybind = super+shift+enter=toggle_split_zoom
+keybind = super+alt+down=goto_split:down
+keybind = super+ctrl+f=toggle_fullscreen
+keybind = super+ctrl+right=resize_split:right,10
+keybind = super+alt+shift+j=write_screen_file:open
+keybind = shift+down=adjust_selection:down
+keybind = ctrl+shift+tab=previous_tab
+keybind = super+n=new_window
+keybind = super+alt+left=goto_split:left
+keybind = super+page_down=scroll_page_down
+keybind = super+alt+shift+w=close_all_windows
+keybind = super+alt+up=goto_split:up
+keybind = super+shift+v=paste_from_selection
+keybind = super+left_bracket=goto_split:previous
+keybind = super+physical:nine=last_tab
+keybind = super+right_bracket=goto_split:next
+keybind = super+end=scroll_to_bottom
+keybind = super+shift+j=write_screen_file:paste
+keybind = super+shift+d=new_split:down
+keybind = super+zero=reset_font_size
+keybind = super+physical:five=goto_tab:5
+keybind = shift+home=adjust_selection:home
+keybind = super+physical:seven=goto_tab:7
+keybind = super+up=jump_to_prompt:-1
+keybind = super+k=clear_screen
+keybind = super+physical:two=goto_tab:2
+keybind = super+physical:six=goto_tab:6
+keybind = super+v=paste_from_clipboard
+
+# Horizontal window padding. This applies padding between the terminal cells
+# and the left and right window borders. The value is in points, meaning that
+# it will be scaled appropriately for screen DPI.
+#
+# If this value is set too large, the screen will render nothing, because the
+# grid will be completely squished by the padding. It is up to you as the user
+# to pick a reasonable value. If you pick an unreasonable value, a warning
+# will appear in the logs.
+#
+# Changing this configuration at runtime will only affect new terminals, i.e.
+# new windows, tabs, etc.
+#
+# To set a different left and right padding, specify two numerical values
+# separated by a comma. For example, `window-padding-x = 2,4` will set the
+# left padding to 2 and the right padding to 4. If you want to set both
+# paddings to the same value, you can use a single value. For example,
+# `window-padding-x = 2` will set both paddings to 2.
+window-padding-x = 2
+
+# Vertical window padding. This applies padding between the terminal cells and
+# the top and bottom window borders. The value is in points, meaning that it
+# will be scaled appropriately for screen DPI.
+#
+# If this value is set too large, the screen will render nothing, because the
+# grid will be completely squished by the padding. It is up to you as the user
+# to pick a reasonable value. If you pick an unreasonable value, a warning
+# will appear in the logs.
+#
+# Changing this configuration at runtime will only affect new terminals,
+# i.e. new windows, tabs, etc.
+#
+# To set a different top and bottom padding, specify two numerical values
+# separated by a comma. For example, `window-padding-y = 2,4` will set the
+# top padding to 2 and the bottom padding to 4. If you want to set both
+# paddings to the same value, you can use a single value. For example,
+# `window-padding-y = 2` will set both paddings to 2.
+window-padding-y = 2
+
+# The viewport dimensions are usually not perfectly divisible by the cell
+# size. In this case, some extra padding on the end of a column and the bottom
+# of the final row may exist. If this is `true`, then this extra padding
+# is automatically balanced between all four edges to minimize imbalance on
+# one side. If this is `false`, the top left grid cell will always hug the
+# edge with zero padding other than what may be specified with the other
+# `window-padding` options.
+#
+# If other `window-padding` fields are set and this is `true`, this will still
+# apply. The other padding is applied first and may affect how many grid cells
+# actually exist, and this is applied last in order to balance the padding
+# given a certain viewport size and grid cell size.
+window-padding-balance = false
+
+# The color of the padding area of the window. Valid values are:
+#
+# * `background` - The background color specified in `background`.
+# * `extend` - Extend the background color of the nearest grid cell.
+# * `extend-always` - Same as "extend" but always extends without applying
+# any of the heuristics that disable extending noted below.
+#
+# The "extend" value will be disabled in certain scenarios. On primary
+# screen applications (e.g. not something like Neovim), the color will not
+# be extended vertically if any of the following are true:
+#
+# * The nearest row has any cells that have the default background color.
+# The thinking is that in this case, the default background color looks
+# fine as a padding color.
+# * The nearest row is a prompt row (requires shell integration). The
+# thinking here is that prompts often contain powerline glyphs that
+# do not look good extended.
+# * The nearest row contains a perfect fit powerline character. These
+# don't look good extended.
+#
+window-padding-color = background
+
+# Synchronize rendering with the screen refresh rate. If true, this will
+# minimize tearing and align redraws with the screen but may cause input
+# latency. If false, this will maximize redraw frequency but may cause tearing,
+# and under heavy load may use more CPU and power.
+#
+# This defaults to true because out-of-sync rendering on macOS can
+# cause kernel panics (macOS 14.4+) and performance issues for external
+# displays over some hardware such as DisplayLink. If you want to minimize
+# input latency, set this to false with the known aforementioned risks.
+#
+# Changing this value at runtime will only affect new terminals.
+#
+# This setting is only supported currently on macOS.
+window-vsync = true
+
+# If true, new windows and tabs will inherit the working directory of the
+# previously focused window. If no window was previously focused, the default
+# working directory will be used (the `working-directory` option).
+window-inherit-working-directory = true
+
+# If true, new windows and tabs will inherit the font size of the previously
+# focused window. If no window was previously focused, the default font size
+# will be used. If this is false, the default font size specified in the
+# configuration `font-size` will be used.
+window-inherit-font-size = true
+
+# Configure a preference for window decorations. This setting specifies
+# a _preference_; the actual OS, desktop environment, window manager, etc.
+# may override this preference. Ghostty will do its best to respect this
+# preference but it may not always be possible.
+#
+# Valid values:
+#
+# * `none` - All window decorations will be disabled. Titlebar,
+# borders, etc. will not be shown. On macOS, this will also disable
+# tabs (enforced by the system).
+#
+# * `auto` - Automatically decide to use either client-side or server-side
+# decorations based on the detected preferences of the current OS and
+# desktop environment. This option usually makes Ghostty look the most
+# "native" for your desktop.
+#
+# * `client` - Prefer client-side decorations.
+#
+# * `server` - Prefer server-side decorations. This is only relevant
+# on Linux with GTK, either on X11, or Wayland on a compositor that
+# supports the `org_kde_kwin_server_decoration` protocol (e.g. KDE Plasma,
+# but almost any non-GNOME desktop supports this protocol).
+#
+# If `server` is set but the environment doesn't support server-side
+# decorations, client-side decorations will be used instead.
+#
+# The default value is `auto`.
+#
+# For the sake of backwards compatibility and convenience, this setting also
+# accepts boolean true and false values. If set to `true`, this is equivalent
+# to `auto`. If set to `false`, this is equivalent to `none`.
+# This is convenient for users who live primarily on systems that don't
+# differentiate between client and server-side decorations (e.g. macOS and
+# Windows).
+#
+# The "toggle_window_decorations" keybind action can be used to create
+# a keybinding to toggle this setting at runtime. This will always toggle
+# back to "auto" if the current value is "none" (this is an issue
+# that will be fixed in the future).
+#
+# Changing this configuration in your configuration and reloading will
+# only affect new windows. Existing windows will not be affected.
+#
+# macOS: To hide the titlebar without removing the native window borders
+# or rounded corners, use `macos-titlebar-style = hidden` instead.
+window-decoration = auto
+
+# The font that will be used for the application's window and tab titles.
+#
+# If this setting is left unset, the system default font will be used.
+#
+# Note: any font available on the system may be used, this font is not
+# required to be a fixed-width font.
+window-title-font-family =
+
+# The text that will be displayed in the subtitle of the window. Valid values:
+#
+# * `false` - Disable the subtitle.
+# * `working-directory` - Set the subtitle to the working directory of the
+# surface.
+#
+# This feature is only supported on GTK with Adwaita enabled.
+window-subtitle = false
+
+# The theme to use for the windows. Valid values:
+#
+# * `auto` - Determine the theme based on the configured terminal
+# background color. This has no effect if the "theme" configuration
+# has separate light and dark themes. In that case, the behavior
+# of "auto" is equivalent to "system".
+# * `system` - Use the system theme.
+# * `light` - Use the light theme regardless of system theme.
+# * `dark` - Use the dark theme regardless of system theme.
+# * `ghostty` - Use the background and foreground colors specified in the
+# Ghostty configuration. This is only supported on Linux builds with
+# Adwaita and `gtk-adwaita` enabled.
+#
+# On macOS, if `macos-titlebar-style` is "tabs", the window theme will be
+# automatically set based on the luminosity of the terminal background color.
+# This only applies to terminal windows. This setting will still apply to
+# non-terminal windows within Ghostty.
+#
+# This is currently only supported on macOS and Linux.
+window-theme = auto
+
+# The color space to use when interpreting terminal colors. "Terminal colors"
+# refers to colors specified in your configuration and colors produced by
+# direct-color SGR sequences.
+#
+# Valid values:
+#
+# * `srgb` - Interpret colors in the sRGB color space. This is the default.
+# * `display-p3` - Interpret colors in the Display P3 color space.
+#
+# This setting is currently only supported on macOS.
+window-colorspace = srgb
+
+# The initial window size. This size is in terminal grid cells by default.
+# Both values must be set to take effect. If only one value is set, it is
+# ignored.
+#
+# We don't currently support specifying a size in pixels but a future change
+# can enable that. If this isn't specified, the app runtime will determine
+# some default size.
+#
+# Note that the window manager may put limits on the size or override the
+# size. For example, a tiling window manager may force the window to be a
+# certain size to fit within the grid. There is nothing Ghostty will do about
+# this, but it will make an effort.
+#
+# Sizes larger than the screen size will be clamped to the screen size.
+# This can be used to create a maximized-by-default window size.
+#
+# This will not affect new tabs, splits, or other nested terminal elements.
+# This only affects the initial window size of any new window. Changing this
+# value will not affect the size of the window after it has been created. This
+# is only used for the initial size.
+#
+# BUG: On Linux with GTK, the calculated window size will not properly take
+# into account window decorations. As a result, the grid dimensions will not
+# exactly match this configuration. If window decorations are disabled (see
+# `window-decoration`), then this will work as expected.
+#
+# Windows smaller than 10 wide by 4 high are not allowed.
+window-height = 0
+
+window-width = 0
+# The starting window position. This position is in pixels and is relative
+# to the top-left corner of the primary monitor. Both values must be set to take
+# effect. If only one value is set, it is ignored.
+#
+# Note that the window manager may put limits on the position or override
+# the position. For example, a tiling window manager may force the window
+# to be a certain position to fit within the grid. There is nothing Ghostty
+# will do about this, but it will make an effort.
+#
+# Also note that negative values are also up to the operating system and
+# window manager. Some window managers may not allow windows to be placed
+# off-screen.
+#
+# Invalid positions are runtime-specific, but generally the positions are
+# clamped to the nearest valid position.
+#
+# On macOS, the window position is relative to the top-left corner of
+# the visible screen area. This means that if the menu bar is visible, the
+# window will be placed below the menu bar.
+#
+# Note: this is only supported on macOS and Linux GLFW builds. The GTK
+# runtime does not support setting the window position (this is a limitation
+# of GTK 4.0).
+window-position-x =
+
+window-position-y =
+# Whether to enable saving and restoring window state. Window state includes
+# their position, size, tabs, splits, etc. Some window state requires shell
+# integration, such as preserving working directories. See `shell-integration`
+# for more information.
+#
+# There are three valid values for this configuration:
+#
+# * `default` will use the default system behavior. On macOS, this
+# will only save state if the application is forcibly terminated
+# or if it is configured systemwide via Settings.app.
+#
+# * `never` will never save window state.
+#
+# * `always` will always save window state whenever Ghostty is exited.
+#
+# If you change this value to `never` while Ghostty is not running, the next
+# Ghostty launch will NOT restore the window state.
+#
+# If you change this value to `default` while Ghostty is not running and the
+# previous exit saved state, the next Ghostty launch will still restore the
+# window state. This is because Ghostty cannot know if the previous exit was
+# due to a forced save or not (macOS doesn't provide this information).
+#
+# If you change this value so that window state is saved while Ghostty is not
+# running, the previous window state will not be restored because Ghostty only
+# saves state on exit if this is enabled.
+#
+# The default value is `default`.
+#
+# This is currently only supported on macOS. This has no effect on Linux.
+window-save-state = default
+
+# Resize the window in discrete increments of the focused surface's cell size.
+# If this is disabled, surfaces are resized in pixel increments. Currently
+# only supported on macOS.
+window-step-resize = false
+
+# The position where new tabs are created. Valid values:
+#
+# * `current` - Insert the new tab after the currently focused tab,
+# or at the end if there are no focused tabs.
+#
+# * `end` - Insert the new tab at the end of the tab list.
+window-new-tab-position = current
+
+# Background color for the window titlebar. This only takes effect if
+# window-theme is set to ghostty. Currently only supported in the GTK app
+# runtime.
+#
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+window-titlebar-background =
+
+# Foreground color for the window titlebar. This only takes effect if
+# window-theme is set to ghostty. Currently only supported in the GTK app
+# runtime.
+#
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+window-titlebar-foreground =
+
+# This controls when resize overlays are shown. Resize overlays are a
+# transient popup that shows the size of the terminal while the surfaces are
+# being resized. The possible options are:
+#
+# * `always` - Always show resize overlays.
+# * `never` - Never show resize overlays.
+# * `after-first` - The resize overlay will not appear when the surface
+# is first created, but will show up if the surface is
+# subsequently resized.
+#
+# The default is `after-first`.
+resize-overlay = after-first
+
+# If resize overlays are enabled, this controls the position of the overlay.
+# The possible options are:
+#
+# * `center`
+# * `top-left`
+# * `top-center`
+# * `top-right`
+# * `bottom-left`
+# * `bottom-center`
+# * `bottom-right`
+#
+# The default is `center`.
+resize-overlay-position = center
+
+# If resize overlays are enabled, this controls how long the overlay is
+# visible on the screen before it is hidden. The default is ¾ of a second or
+# 750 ms.
+#
+# The duration is specified as a series of numbers followed by time units.
+# Whitespace is allowed between numbers and units. Each number and unit will
+# be added together to form the total duration.
+#
+# The allowed time units are as follows:
+#
+# * `y` - 365 SI days, or 8760 hours, or 31536000 seconds. No adjustments
+# are made for leap years or leap seconds.
+# * `d` - one SI day, or 86400 seconds.
+# * `h` - one hour, or 3600 seconds.
+# * `m` - one minute, or 60 seconds.
+# * `s` - one second.
+# * `ms` - one millisecond, or 0.001 second.
+# * `us` or `µs` - one microsecond, or 0.000001 second.
+# * `ns` - one nanosecond, or 0.000000001 second.
+#
+# Examples:
+# * `1h30m`
+# * `45s`
+#
+# Units can be repeated and will be added together. This means that
+# `1h1h` is equivalent to `2h`. This is confusing and should be avoided.
+# A future update may disallow this.
+#
+# The maximum value is `584y 49w 23h 34m 33s 709ms 551µs 615ns`. Any
+# value larger than this will be clamped to the maximum value.
+resize-overlay-duration = 750ms
+
+# If true, when there are multiple split panes, the mouse selects the pane
+# that is focused. This only applies to the currently focused window; e.g.
+# mousing over a split in an unfocused window will not focus that split
+# and bring the window to front.
+#
+# Default is false.
+focus-follows-mouse = false
+
+# Whether to allow programs running in the terminal to read/write to the
+# system clipboard (OSC 52, for googling). The default is to allow clipboard
+# reading after prompting the user and allow writing unconditionally.
+#
+# Valid values are:
+#
+# * `ask`
+# * `allow`
+# * `deny`
+#
+clipboard-read = ask
+
+clipboard-write = allow
+# Trims trailing whitespace on data that is copied to the clipboard. This does
+# not affect data sent to the clipboard via `clipboard-write`.
+clipboard-trim-trailing-spaces = true
+
+# Require confirmation before pasting text that appears unsafe. This helps
+# prevent a "copy/paste attack" where a user may accidentally execute unsafe
+# commands by pasting text with newlines.
+clipboard-paste-protection = true
+
+# If true, bracketed pastes will be considered safe. By default, bracketed
+# pastes are considered safe. "Bracketed" pastes are pastes while the running
+# program has bracketed paste mode enabled (a setting set by the running
+# program, not the terminal emulator).
+clipboard-paste-bracketed-safe = true
+
+# Enables or disabled title reporting (CSI 21 t). This escape sequence
+# allows the running program to query the terminal title. This is a common
+# security issue and is disabled by default.
+#
+# Warning: This can expose sensitive information at best and enable
+# arbitrary code execution at worst (with a maliciously crafted title
+# and a minor amount of user interaction).
+title-report = false
+
+# The total amount of bytes that can be used for image data (e.g. the Kitty
+# image protocol) per terminal screen. The maximum value is 4,294,967,295
+# (4GiB). The default is 320MB. If this is set to zero, then all image
+# protocols will be disabled.
+#
+# This value is separate for primary and alternate screens so the effective
+# limit per surface is double.
+image-storage-limit = 320000000
+
+# Whether to automatically copy selected text to the clipboard. `true`
+# will prefer to copy to the selection clipboard, otherwise it will copy to
+# the system clipboard.
+#
+# The value `clipboard` will always copy text to the selection clipboard
+# as well as the system clipboard.
+#
+# Middle-click paste will always use the selection clipboard. Middle-click
+# paste is always enabled even if this is `false`.
+#
+# The default value is true on Linux and macOS.
+copy-on-select = true
+
+# The time in milliseconds between clicks to consider a click a repeat
+# (double, triple, etc.) or an entirely new single click. A value of zero will
+# use a platform-specific default. The default on macOS is determined by the
+# OS settings. On every other platform it is 500ms.
+click-repeat-interval = 0
+
+# Additional configuration files to read. This configuration can be repeated
+# to read multiple configuration files. Configuration files themselves can
+# load more configuration files. Paths are relative to the file containing the
+# `config-file` directive. For command-line arguments, paths are relative to
+# the current working directory.
+#
+# Prepend a ? character to the file path to suppress errors if the file does
+# not exist. If you want to include a file that begins with a literal ?
+# character, surround the file path in double quotes (").
+#
+# Cycles are not allowed. If a cycle is detected, an error will be logged and
+# the configuration file will be ignored.
+#
+# Configuration files are loaded after the configuration they're defined
+# within in the order they're defined. **THIS IS A VERY SUBTLE BUT IMPORTANT
+# POINT.** To put it another way: configuration files do not take effect
+# until after the entire configuration is loaded. For example, in the
+# configuration below:
+#
+# ```
+# config-file = "foo"
+# a = 1
+# ```
+#
+# If "foo" contains `a = 2`, the final value of `a` will be 2, because
+# `foo` is loaded after the configuration file that configures the
+# nested `config-file` value.
+config-file =
+
+# When this is true, the default configuration file paths will be loaded.
+# The default configuration file paths are currently only the XDG
+# config path ($XDG_CONFIG_HOME/ghostty/config).
+#
+# If this is false, the default configuration paths will not be loaded.
+# This is targeted directly at using Ghostty from the CLI in a way
+# that minimizes external effects.
+#
+# This is a CLI-only configuration. Setting this in a configuration file
+# will have no effect. It is not an error, but it will not do anything.
+# This configuration can only be set via CLI arguments.
+config-default-files = true
+
+# Confirms that a surface should be closed before closing it.
+#
+# This defaults to `true`. If set to `false`, surfaces will close without
+# any confirmation. This can also be set to `always`, which will always
+# confirm closing a surface, even if shell integration says a process isn't
+# running.
+confirm-close-surface = true
+
+# Whether or not to quit after the last surface is closed.
+#
+# This defaults to `false` on macOS since that is standard behavior for
+# a macOS application. On Linux, this defaults to `true` since that is
+# generally expected behavior.
+#
+# On Linux, if this is `true`, Ghostty can delay quitting fully until a
+# configurable amount of time has passed after the last window is closed.
+# See the documentation of `quit-after-last-window-closed-delay`.
+quit-after-last-window-closed = false
+
+# Controls how long Ghostty will stay running after the last open surface has
+# been closed. This only has an effect if `quit-after-last-window-closed` is
+# also set to `true`.
+#
+# The minimum value for this configuration is `1s`. Any values lower than
+# this will be clamped to `1s`.
+#
+# The duration is specified as a series of numbers followed by time units.
+# Whitespace is allowed between numbers and units. Each number and unit will
+# be added together to form the total duration.
+#
+# The allowed time units are as follows:
+#
+# * `y` - 365 SI days, or 8760 hours, or 31536000 seconds. No adjustments
+# are made for leap years or leap seconds.
+# * `d` - one SI day, or 86400 seconds.
+# * `h` - one hour, or 3600 seconds.
+# * `m` - one minute, or 60 seconds.
+# * `s` - one second.
+# * `ms` - one millisecond, or 0.001 second.
+# * `us` or `µs` - one microsecond, or 0.000001 second.
+# * `ns` - one nanosecond, or 0.000000001 second.
+#
+# Examples:
+# * `1h30m`
+# * `45s`
+#
+# Units can be repeated and will be added together. This means that
+# `1h1h` is equivalent to `2h`. This is confusing and should be avoided.
+# A future update may disallow this.
+#
+# The maximum value is `584y 49w 23h 34m 33s 709ms 551µs 615ns`. Any
+# value larger than this will be clamped to the maximum value.
+#
+# By default `quit-after-last-window-closed-delay` is unset and
+# Ghostty will quit immediately after the last window is closed if
+# `quit-after-last-window-closed` is `true`.
+#
+# Only implemented on Linux.
+quit-after-last-window-closed-delay =
+
+# This controls whether an initial window is created when Ghostty
+# is run. Note that if `quit-after-last-window-closed` is `true` and
+# `quit-after-last-window-closed-delay` is set, setting `initial-window` to
+# `false` will mean that Ghostty will quit after the configured delay if no
+# window is ever created. Only implemented on Linux and macOS.
+initial-window = true
+
+# The position of the "quick" terminal window. To learn more about the
+# quick terminal, see the documentation for the `toggle_quick_terminal`
+# binding action.
+#
+# Valid values are:
+#
+# * `top` - Terminal appears at the top of the screen.
+# * `bottom` - Terminal appears at the bottom of the screen.
+# * `left` - Terminal appears at the left of the screen.
+# * `right` - Terminal appears at the right of the screen.
+# * `center` - Terminal appears at the center of the screen.
+#
+# Changing this configuration requires restarting Ghostty completely.
+#
+# Note: There is no default keybind for toggling the quick terminal.
+# To enable this feature, bind the `toggle_quick_terminal` action to a key.
+quick-terminal-position = top
+
+# The screen where the quick terminal should show up.
+#
+# Valid values are:
+#
+# * `main` - The screen that the operating system recommends as the main
+# screen. On macOS, this is the screen that is currently receiving
+# keyboard input. This screen is defined by the operating system and
+# not chosen by Ghostty.
+#
+# * `mouse` - The screen that the mouse is currently hovered over.
+#
+# * `macos-menu-bar` - The screen that contains the macOS menu bar as
+# set in the display settings on macOS. This is a bit confusing because
+# every screen on macOS has a menu bar, but this is the screen that
+# contains the primary menu bar.
+#
+# The default value is `main` because this is the recommended screen
+# by the operating system.
+quick-terminal-screen = main
+
+# Duration (in seconds) of the quick terminal enter and exit animation.
+# Set it to 0 to disable animation completely. This can be changed at
+# runtime.
+quick-terminal-animation-duration = 0.2
+
+# Automatically hide the quick terminal when focus shifts to another window.
+# Set it to false for the quick terminal to remain open even when it loses focus.
+quick-terminal-autohide = true
+
+# This configuration option determines the behavior of the quick terminal
+# when switching between macOS spaces. macOS spaces are virtual desktops
+# that can be manually created or are automatically created when an
+# application is in full-screen mode.
+#
+# Valid values are:
+#
+# * `move` - When switching to another space, the quick terminal will
+# also moved to the current space.
+#
+# * `remain` - The quick terminal will stay only in the space where it
+# was originally opened and will not follow when switching to another
+# space.
+#
+# The default value is `move`.
+quick-terminal-space-behavior = move
+
+# Whether to enable shell integration auto-injection or not. Shell integration
+# greatly enhances the terminal experience by enabling a number of features:
+#
+# * Working directory reporting so new tabs, splits inherit the
+# previous terminal's working directory.
+#
+# * Prompt marking that enables the "jump_to_prompt" keybinding.
+#
+# * If you're sitting at a prompt, closing a terminal will not ask
+# for confirmation.
+#
+# * Resizing the window with a complex prompt usually paints much
+# better.
+#
+# Allowable values are:
+#
+# * `none` - Do not do any automatic injection. You can still manually
+# configure your shell to enable the integration.
+#
+# * `detect` - Detect the shell based on the filename.
+#
+# * `bash`, `elvish`, `fish`, `zsh` - Use this specific shell injection scheme.
+#
+# The default value is `detect`.
+shell-integration = detect
+
+# Shell integration features to enable. These require our shell integration
+# to be loaded, either automatically via shell-integration or manually.
+#
+# The format of this is a list of features to enable separated by commas. If
+# you prefix a feature with `no-` then it is disabled. If you omit a feature,
+# its default value is used, so you must explicitly disable features you don't
+# want. You can also use `true` or `false` to turn all features on or off.
+#
+# Available features:
+#
+# * `cursor` - Set the cursor to a blinking bar at the prompt.
+#
+# * `sudo` - Set sudo wrapper to preserve terminfo.
+#
+# * `title` - Set the window title via shell integration.
+#
+# Example: `cursor`, `no-cursor`, `sudo`, `no-sudo`, `title`, `no-title`
+shell-integration-features = cursor,no-sudo,title
+
+# Sets the reporting format for OSC sequences that request color information.
+# Ghostty currently supports OSC 10 (foreground), OSC 11 (background), and
+# OSC 4 (256 color palette) queries, and by default the reported values
+# are scaled-up RGB values, where each component are 16 bits. This is how
+# most terminals report these values. However, some legacy applications may
+# require 8-bit, unscaled, components. We also support turning off reporting
+# altogether. The components are lowercase hex values.
+#
+# Allowable values are:
+#
+# * `none` - OSC 4/10/11 queries receive no reply
+#
+# * `8-bit` - Color components are return unscaled, e.g. `rr/gg/bb`
+#
+# * `16-bit` - Color components are returned scaled, e.g. `rrrr/gggg/bbbb`
+#
+# The default value is `16-bit`.
+osc-color-report-format = 16-bit
+
+# If true, allows the "KAM" mode (ANSI mode 2) to be used within
+# the terminal. KAM disables keyboard input at the request of the
+# application. This is not a common feature and is not recommended
+# to be enabled. This will not be documented further because
+# if you know you need KAM, you know. If you don't know if you
+# need KAM, you don't need it.
+vt-kam-allowed = false
+
+# Custom shaders to run after the default shaders. This is a file path
+# to a GLSL-syntax shader for all platforms.
+#
+# Warning: Invalid shaders can cause Ghostty to become unusable such as by
+# causing the window to be completely black. If this happens, you can
+# unset this configuration to disable the shader.
+#
+# On Linux, this requires OpenGL 4.2. Ghostty typically only requires
+# OpenGL 3.3, but custom shaders push that requirement up to 4.2.
+#
+# The shader API is identical to the Shadertoy API: you specify a `mainImage`
+# function and the available uniforms match Shadertoy. The iChannel0 uniform
+# is a texture containing the rendered terminal screen.
+#
+# If the shader fails to compile, the shader will be ignored. Any errors
+# related to shader compilation will not show up as configuration errors
+# and only show up in the log, since shader compilation happens after
+# configuration loading on the dedicated render thread. For interactive
+# development, use [shadertoy.com](https://shadertoy.com).
+#
+# This can be repeated multiple times to load multiple shaders. The shaders
+# will be run in the order they are specified.
+#
+# Changing this value at runtime and reloading the configuration will only
+# affect new windows, tabs, and splits.
+custom-shader =
+
+# If `true` (default), the focused terminal surface will run an animation
+# loop when custom shaders are used. This uses slightly more CPU (generally
+# less than 10%) but allows the shader to animate. This only runs if there
+# are custom shaders and the terminal is focused.
+#
+# If this is set to `false`, the terminal and custom shader will only render
+# when the terminal is updated. This is more efficient but the shader will
+# not animate.
+#
+# This can also be set to `always`, which will always run the animation
+# loop regardless of whether the terminal is focused or not. The animation
+# loop will still only run when custom shaders are used. Note that this
+# will use more CPU per terminal surface and can become quite expensive
+# depending on the shader and your terminal usage.
+#
+# This value can be changed at runtime and will affect all currently
+# open terminals.
+custom-shader-animation = true
+
+# Control the in-app notifications that Ghostty shows.
+#
+# On Linux (GTK) with Adwaita, in-app notifications show up as toasts. Toasts
+# appear overlaid on top of the terminal window. They are used to show
+# information that is not critical but may be important.
+#
+# Possible notifications are:
+#
+# - `clipboard-copy` (default: true) - Show a notification when text is copied
+# to the clipboard.
+#
+# To specify a notification to enable, specify the name of the notification.
+# To specify a notification to disable, prefix the name with `no-`. For
+# example, to disable `clipboard-copy`, set this configuration to
+# `no-clipboard-copy`. To enable it, set this configuration to `clipboard-copy`.
+#
+# Multiple notifications can be enabled or disabled by separating them
+# with a comma.
+#
+# A value of "false" will disable all notifications. A value of "true" will
+# enable all notifications.
+#
+# This configuration only applies to GTK with Adwaita enabled.
+app-notifications = clipboard-copy
+
+# If anything other than false, fullscreen mode on macOS will not use the
+# native fullscreen, but make the window fullscreen without animations and
+# using a new space. It's faster than the native fullscreen mode since it
+# doesn't use animations.
+#
+# Important: tabs DO NOT WORK in this mode. Non-native fullscreen removes
+# the titlebar and macOS native tabs require the titlebar. If you use tabs,
+# you should not use this mode.
+#
+# If you fullscreen a window with tabs, the currently focused tab will
+# become fullscreen while the others will remain in a separate window in
+# the background. You can switch to that window using normal window-switching
+# keybindings such as command+tilde. When you exit fullscreen, the window
+# will return to the tabbed state it was in before.
+#
+# Allowable values are:
+#
+# * `visible-menu` - Use non-native macOS fullscreen, keep the menu bar visible
+# * `true` - Use non-native macOS fullscreen, hide the menu bar
+# * `false` - Use native macOS fullscreen
+#
+# Changing this option at runtime works, but will only apply to the next
+# time the window is made fullscreen. If a window is already fullscreen,
+# it will retain the previous setting until fullscreen is exited.
+macos-non-native-fullscreen = false
+
+# The style of the macOS titlebar. Available values are: "native",
+# "transparent", "tabs", and "hidden".
+#
+# The "native" style uses the native macOS titlebar with zero customization.
+# The titlebar will match your window theme (see `window-theme`).
+#
+# The "transparent" style is the same as "native" but the titlebar will
+# be transparent and allow your window background color to come through.
+# This makes a more seamless window appearance but looks a little less
+# typical for a macOS application and may not work well with all themes.
+#
+# The "transparent" style will also update in real-time to dynamic
+# changes to the window background color, e.g. via OSC 11. To make this
+# more aesthetically pleasing, this only happens if the terminal is
+# a window, tab, or split that borders the top of the window. This
+# avoids a disjointed appearance where the titlebar color changes
+# but all the topmost terminals don't match.
+#
+# The "tabs" style is a completely custom titlebar that integrates the
+# tab bar into the titlebar. This titlebar always matches the background
+# color of the terminal. There are some limitations to this style:
+# On macOS 13 and below, saved window state will not restore tabs correctly.
+# macOS 14 does not have this issue and any other macOS version has not
+# been tested.
+#
+# The "hidden" style hides the titlebar. Unlike `window-decoration = false`,
+# however, it does not remove the frame from the window or cause it to have
+# squared corners. Changing to or from this option at run-time may affect
+# existing windows in buggy ways.
+#
+# When "hidden", the top titlebar area can no longer be used for dragging
+# the window. To drag the window, you can use option+click on the resizable
+# areas of the frame to drag the window. This is a standard macOS behavior
+# and not something Ghostty enables.
+#
+# The default value is "transparent". This is an opinionated choice
+# but its one I think is the most aesthetically pleasing and works in
+# most cases.
+#
+# Changing this option at runtime only applies to new windows.
+macos-titlebar-style = transparent
+
+# Whether the proxy icon in the macOS titlebar is visible. The proxy icon
+# is the icon that represents the folder of the current working directory.
+# You can see this very clearly in the macOS built-in Terminal.app
+# titlebar.
+#
+# The proxy icon is only visible with the native macOS titlebar style.
+#
+# Valid values are:
+#
+# * `visible` - Show the proxy icon.
+# * `hidden` - Hide the proxy icon.
+#
+# The default value is `visible`.
+#
+# This setting can be changed at runtime and will affect all currently
+# open windows but only after their working directory changes again.
+# Therefore, to make this work after changing the setting, you must
+# usually `cd` to a different directory, open a different file in an
+# editor, etc.
+macos-titlebar-proxy-icon = visible
+
+# macOS doesn't have a distinct "alt" key and instead has the "option"
+# key which behaves slightly differently. On macOS by default, the
+# option key plus a character will sometimes produces a Unicode character.
+# For example, on US standard layouts option-b produces "∫". This may be
+# undesirable if you want to use "option" as an "alt" key for keybindings
+# in terminal programs or shells.
+#
+# This configuration lets you change the behavior so that option is treated
+# as alt.
+#
+# The default behavior (unset) will depend on your active keyboard
+# layout. If your keyboard layout is one of the keyboard layouts listed
+# below, then the default value is "true". Otherwise, the default
+# value is "false". Keyboard layouts with a default value of "true" are:
+#
+# - U.S. Standard
+# - U.S. International
+#
+# Note that if an *Option*-sequence doesn't produce a printable character, it
+# will be treated as *Alt* regardless of this setting. (e.g. `alt+ctrl+a`).
+#
+# Explicit values that can be set:
+#
+# If `true`, the *Option* key will be treated as *Alt*. This makes terminal
+# sequences expecting *Alt* to work properly, but will break Unicode input
+# sequences on macOS if you use them via the *Alt* key.
+#
+# You may set this to `false` to restore the macOS *Alt* key unicode
+# sequences but this will break terminal sequences expecting *Alt* to work.
+#
+# The values `left` or `right` enable this for the left or right *Option*
+# key, respectively.
+#
+# This does not work with GLFW builds.
+macos-option-as-alt =
+
+# Whether to enable the macOS window shadow. The default value is true.
+# With some window managers and window transparency settings, you may
+# find false more visually appealing.
+macos-window-shadow = true
+
+# If true, Ghostty on macOS will automatically enable the "Secure Input"
+# feature when it detects that a password prompt is being displayed.
+#
+# "Secure Input" is a macOS security feature that prevents applications from
+# reading keyboard events. This can always be enabled manually using the
+# `Ghostty > Secure Keyboard Entry` menu item.
+#
+# Note that automatic password prompt detection is based on heuristics
+# and may not always work as expected. Specifically, it does not work
+# over SSH connections, but there may be other cases where it also
+# doesn't work.
+#
+# A reason to disable this feature is if you find that it is interfering
+# with legitimate accessibility software (or software that uses the
+# accessibility APIs), since secure input prevents any application from
+# reading keyboard events.
+macos-auto-secure-input = true
+
+# If true, Ghostty will show a graphical indication when secure input is
+# enabled. This indication is generally recommended to know when secure input
+# is enabled.
+#
+# Normally, secure input is only active when a password prompt is displayed
+# or it is manually (and typically temporarily) enabled. However, if you
+# always have secure input enabled, the indication can be distracting and
+# you may want to disable it.
+macos-secure-input-indication = true
+
+# Customize the macOS app icon.
+#
+# This only affects the icon that appears in the dock, application
+# switcher, etc. This does not affect the icon in Finder because
+# that is controlled by a hardcoded value in the signed application
+# bundle and can't be changed at runtime. For more details on what
+# exactly is affected, see the `NSApplication.icon` Apple documentation;
+# that is the API that is being used to set the icon.
+#
+# Valid values:
+#
+# * `official` - Use the official Ghostty icon.
+# * `blueprint`, `chalkboard`, `microchip`, `glass`, `holographic`,
+# `paper`, `retro`, `xray` - Official variants of the Ghostty icon
+# hand-created by artists (no AI).
+# * `custom-style` - Use the official Ghostty icon but with custom
+# styles applied to various layers. The custom styles must be
+# specified using the additional `macos-icon`-prefixed configurations.
+# The `macos-icon-ghost-color` and `macos-icon-screen-color`
+# configurations are required for this style.
+#
+# WARNING: The `custom-style` option is _experimental_. We may change
+# the format of the custom styles in the future. We're still finalizing
+# the exact layers and customization options that will be available.
+#
+# Other caveats:
+#
+# * The icon in the update dialog will always be the official icon.
+# This is because the update dialog is managed through a
+# separate framework and cannot be customized without significant
+# effort.
+#
+macos-icon = official
+
+# The material to use for the frame of the macOS app icon.
+#
+# Valid values:
+#
+# * `aluminum` - A brushed aluminum frame. This is the default.
+# * `beige` - A classic 90's computer beige frame.
+# * `plastic` - A glossy, dark plastic frame.
+# * `chrome` - A shiny chrome frame.
+#
+# This only has an effect when `macos-icon` is set to `custom-style`.
+macos-icon-frame = aluminum
+
+# The color of the ghost in the macOS app icon.
+#
+# Note: This configuration is required when `macos-icon` is set to
+# `custom-style`.
+#
+# This only has an effect when `macos-icon` is set to `custom-style`.
+#
+# Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color.
+macos-icon-ghost-color =
+
+# The color of the screen in the macOS app icon.
+#
+# The screen is a gradient so you can specify multiple colors that
+# make up the gradient. Comma-separated colors may be specified as
+# as either hex (`#RRGGBB` or `RRGGBB`) or as named X11 colors.
+#
+# Note: This configuration is required when `macos-icon` is set to
+# `custom-style`.
+#
+# This only has an effect when `macos-icon` is set to `custom-style`.
+macos-icon-screen-color =
+
+# Put every surface (tab, split, window) into a dedicated Linux cgroup.
+#
+# This makes it so that resource management can be done on a per-surface
+# granularity. For example, if a shell program is using too much memory,
+# only that shell will be killed by the oom monitor instead of the entire
+# Ghostty process. Similarly, if a shell program is using too much CPU,
+# only that surface will be CPU-throttled.
+#
+# This will cause startup times to be slower (a hundred milliseconds or so),
+# so the default value is "single-instance." In single-instance mode, only
+# one instance of Ghostty is running (see gtk-single-instance) so the startup
+# time is a one-time cost. Additionally, single instance Ghostty is much
+# more likely to have many windows, tabs, etc. so cgroup isolation is a
+# big benefit.
+#
+# This feature requires systemd. If systemd is unavailable, cgroup
+# initialization will fail. By default, this will not prevent Ghostty
+# from working (see linux-cgroup-hard-fail).
+#
+# Valid values are:
+#
+# * `never` - Never use cgroups.
+# * `always` - Always use cgroups.
+# * `single-instance` - Enable cgroups only for Ghostty instances launched
+# as single-instance applications (see gtk-single-instance).
+#
+linux-cgroup = single-instance
+
+# Memory limit for any individual terminal process (tab, split, window,
+# etc.) in bytes. If this is unset then no memory limit will be set.
+#
+# Note that this sets the "memory.high" configuration for the memory
+# controller, which is a soft limit. You should configure something like
+# systemd-oom to handle killing processes that have too much memory
+# pressure.
+linux-cgroup-memory-limit =
+
+# Number of processes limit for any individual terminal process (tab, split,
+# window, etc.). If this is unset then no limit will be set.
+#
+# Note that this sets the "pids.max" configuration for the process number
+# controller, which is a hard limit.
+linux-cgroup-processes-limit =
+
+# If this is false, then any cgroup initialization (for linux-cgroup)
+# will be allowed to fail and the failure is ignored. This is useful if
+# you view cgroup isolation as a "nice to have" and not a critical resource
+# management feature, because Ghostty startup will not fail if cgroup APIs
+# fail.
+#
+# If this is true, then any cgroup initialization failure will cause
+# Ghostty to exit or new surfaces to not be created.
+#
+# Note: This currently only affects cgroup initialization. Subprocesses
+# must always be able to move themselves into an isolated cgroup.
+linux-cgroup-hard-fail = false
+
+# Enable or disable GTK's OpenGL debugging logs. The default is `true` for
+# debug builds, `false` for all others.
+gtk-opengl-debug = false
+
+# Obsolete configuration that should not be set. This was deprecated in
+# Ghostty 1.1.3 and no longer has any effect. The configuration key will
+# be fully removed in 1.2.0. You can manually override the GSK renderer
+# using standard environment variables such as `GSK_RENDERER` (from GTK).
+gtk-gsk-renderer = default
+
+# If `true`, the Ghostty GTK application will run in single-instance mode:
+# each new `ghostty` process launched will result in a new window if there is
+# already a running process.
+#
+# If `false`, each new ghostty process will launch a separate application.
+#
+# The default value is `desktop` which will default to `true` if Ghostty
+# detects that it was launched from the `.desktop` file such as an app
+# launcher (like Gnome Shell) or by D-Bus activation. If Ghostty is launched
+# from the command line, it will default to `false`.
+#
+# Note that debug builds of Ghostty have a separate single-instance ID
+# so you can test single instance without conflicting with release builds.
+gtk-single-instance = desktop
+
+# When enabled, the full GTK titlebar is displayed instead of your window
+# manager's simple titlebar. The behavior of this option will vary with your
+# window manager.
+#
+# This option does nothing when `window-decoration` is false or when running
+# under macOS.
+#
+# Changing this value at runtime and reloading the configuration will only
+# affect new windows.
+gtk-titlebar = true
+
+# Determines the side of the screen that the GTK tab bar will stick to.
+# Top, bottom, left, right, and hidden are supported. The default is top.
+#
+# If this option has value `left` or `right` when using Adwaita, it falls
+# back to `top`. `hidden`, meaning that tabs don't exist, is not supported
+# without using Adwaita, falling back to `top`.
+#
+# When `hidden` is set and Adwaita is enabled, a tab button displaying the
+# number of tabs will appear in the title bar. It has the ability to open a
+# tab overview for displaying tabs. Alternatively, you can use the
+# `toggle_tab_overview` action in a keybind if your window doesn't have a
+# title bar, or you can switch tabs with keybinds.
+gtk-tabs-location = top
+
+# If this is `true`, the titlebar will be hidden when the window is maximized,
+# and shown when the titlebar is unmaximized. GTK only.
+gtk-titlebar-hide-when-maximized = false
+
+# Determines the appearance of the top and bottom bars when using the
+# Adwaita tab bar. This requires `gtk-adwaita` to be enabled (it is
+# by default).
+#
+# Valid values are:
+#
+# * `flat` - Top and bottom bars are flat with the terminal window.
+# * `raised` - Top and bottom bars cast a shadow on the terminal area.
+# * `raised-border` - Similar to `raised` but the shadow is replaced with a
+# more subtle border.
+#
+# Changing this value at runtime will only affect new windows.
+adw-toolbar-style = raised
+
+# If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
+# are the new typical Gnome style where tabs fill their available space.
+# If you set this to `false` then tabs will only take up space they need,
+# which is the old style.
+gtk-wide-tabs = true
+
+# If `true` (default), Ghostty will enable Adwaita theme support. This
+# will make `window-theme` work properly and will also allow Ghostty to
+# properly respond to system theme changes, light/dark mode changing, etc.
+# This requires a GTK4 desktop with a GTK4 theme.
+#
+# If you are running GTK3 or have a GTK3 theme, you may have to set this
+# to false to get your theme picked up properly. Having this set to true
+# with GTK3 should not cause any problems, but it may not work exactly as
+# expected.
+#
+# This configuration only has an effect if Ghostty was built with
+# Adwaita support.
+gtk-adwaita = true
+
+# Custom CSS files to be loaded.
+#
+# This configuration can be repeated multiple times to load multiple files.
+# Prepend a ? character to the file path to suppress errors if the file does
+# not exist. If you want to include a file that begins with a literal ?
+# character, surround the file path in double quotes (").
+# The file size limit for a single stylesheet is 5MiB.
+gtk-custom-css =
+
+# If `true` (default), applications running in the terminal can show desktop
+# notifications using certain escape sequences such as OSC 9 or OSC 777.
+desktop-notifications = true
+
+# If `true`, the bold text will use the bright color palette.
+bold-is-bright = false
+
+# This will be used to set the `TERM` environment variable.
+# HACK: We set this with an `xterm` prefix because vim uses that to enable key
+# protocols (specifically this will enable `modifyOtherKeys`), among other
+# features. An option exists in vim to modify this: `:set
+# keyprotocol=ghostty:kitty`, however a bug in the implementation prevents it
+# from working properly. https://github.com/vim/vim/pull/13211 fixes this.
+term = xterm-ghostty
+
+# String to send when we receive `ENQ` (`0x05`) from the command that we are
+# running. Defaults to an empty string if not set.
+enquiry-response =
+
+# Control the auto-update functionality of Ghostty. This is only supported
+# on macOS currently, since Linux builds are distributed via package
+# managers that are not centrally controlled by Ghostty.
+#
+# Checking or downloading an update does not send any information to
+# the project beyond standard network information mandated by the
+# underlying protocols. To put it another way: Ghostty doesn't explicitly
+# add any tracking to the update process. The update process works by
+# downloading information about the latest version and comparing it
+# client-side to the current version.
+#
+# Valid values are:
+#
+# * `off` - Disable auto-updates.
+# * `check` - Check for updates and notify the user if an update is
+# available, but do not automatically download or install the update.
+# * `download` - Check for updates, automatically download the update,
+# notify the user, but do not automatically install the update.
+#
+# If unset, we defer to Sparkle's default behavior, which respects the
+# preference stored in the standard user defaults (`defaults(1)`).
+#
+# Changing this value at runtime works after a small delay.
+auto-update =
+
+# The release channel to use for auto-updates.
+#
+# The default value of this matches the release channel of the currently
+# running Ghostty version. If you download a pre-release version of Ghostty
+# then this will be set to `tip` and you will receive pre-release updates.
+# If you download a stable version of Ghostty then this will be set to
+# `stable` and you will receive stable updates.
+#
+# Valid values are:
+#
+# * `stable` - Stable, tagged releases such as "1.0.0".
+# * `tip` - Pre-release versions generated from each commit to the
+# main branch. This is the version that was in use during private
+# beta testing by thousands of people. It is generally stable but
+# will likely have more bugs than the stable channel.
+#
+# Changing this configuration requires a full restart of
+# Ghostty to take effect.
+#
+# This only works on macOS since only macOS has an auto-update feature.
+auto-update-channel =
+
diff --git a/ghostty/ghostty-theme b/ghostty/ghostty-theme
new file mode 100644
index 0000000..4adedec
--- /dev/null
+++ b/ghostty/ghostty-theme
@@ -0,0 +1,29 @@
+background = #0D1116
+foreground = #ffffff
+
+cursor-color = #f94dff
+
+# black
+palette = 0=#0D1116
+palette = 8=#e58f2a
+# red
+palette = 1=#f16c75
+palette = 9=#f16c75
+# green
+palette = 2=#37f499
+palette = 10=#37f499
+# yellow
+palette = 3=#9ad900
+palette = 11=#9ad900
+# blue
+palette = 4=#987afb
+palette = 12=#987afb
+# purple
+palette = 5=#fca6ff
+palette = 13=#fca6ff
+# aqua
+palette = 6=#04d1f9
+palette = 14=#04d1f9
+# white
+palette = 7=#ffffff
+palette = 15=#ffffff
diff --git a/ghostty/reload-config.scpt b/ghostty/reload-config.scpt
new file mode 100644
index 0000000..3d574e4
--- /dev/null
+++ b/ghostty/reload-config.scpt
@@ -0,0 +1,6 @@
+tell application "Ghostty"
+ activate
+ tell application "System Events"
+ keystroke "," using {command down, shift down} -- cmd+shift+,
+ end tell
+end tell
diff --git a/ghostty/shaders/animated-gradient-shader.glsl b/ghostty/shaders/animated-gradient-shader.glsl
new file mode 100644
index 0000000..01b541c
--- /dev/null
+++ b/ghostty/shaders/animated-gradient-shader.glsl
@@ -0,0 +1,40 @@
+// credits: https://github.com/unkn0wncode
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ // Create seamless gradient animation
+ float speed = 0.2;
+ float gradientFactor = (uv.x + uv.y) / 2.0;
+
+ // Use smoothstep and multiple sin waves for smoother transition
+ float t = sin(iTime * speed) * 0.5 + 0.5;
+ gradientFactor = smoothstep(0.0, 1.0, gradientFactor);
+
+ // Create smooth circular animation
+ float angle = iTime * speed;
+ vec3 color1 = vec3(0.1, 0.1, 0.5);
+ vec3 color2 = vec3(0.5, 0.1, 0.1);
+ vec3 color3 = vec3(0.1, 0.5, 0.1);
+
+ // Smooth interpolation between colors using multiple mix operations
+ vec3 gradientStartColor = mix(
+ mix(color1, color2, smoothstep(0.0, 1.0, sin(angle) * 0.5 + 0.5)),
+ color3,
+ smoothstep(0.0, 1.0, sin(angle + 2.0) * 0.5 + 0.5)
+ );
+
+ vec3 gradientEndColor = mix(
+ mix(color2, color3, smoothstep(0.0, 1.0, sin(angle + 1.0) * 0.5 + 0.5)),
+ color1,
+ smoothstep(0.0, 1.0, sin(angle + 3.0) * 0.5 + 0.5)
+ );
+
+ vec3 gradientColor = mix(gradientStartColor, gradientEndColor, gradientFactor);
+
+ vec4 terminalColor = texture(iChannel0, uv);
+ float mask = 1.0 - step(0.5, dot(terminalColor.rgb, vec3(1.0)));
+ vec3 blendedColor = mix(terminalColor.rgb, gradientColor, mask);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/bettercrt.glsl b/ghostty/shaders/bettercrt.glsl
new file mode 100644
index 0000000..8f58b89
--- /dev/null
+++ b/ghostty/shaders/bettercrt.glsl
@@ -0,0 +1,33 @@
+// Original shader collected from: https://www.shadertoy.com/view/WsVSzV
+// Licensed under Shadertoy's default since the original creator didn't provide any license. (CC BY NC SA 3.0)
+// Slight modifications were made to give a green-ish effect.
+
+// This shader was modified by April Hall (arithefirst)
+// Sourced from https://github.com/m-ahdal/ghostty-shaders/blob/main/retro-terminal.glsl
+// Changes made:
+// - Removed tint
+// - Made the boundaries match ghostty's background color
+
+float warp = 0.25; // simulate curvature of CRT monitor
+float scan = 0.50; // simulate darkness between scanlines
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ // squared distance from center
+ vec2 uv = fragCoord / iResolution.xy;
+ vec2 dc = abs(0.5 - uv);
+ dc *= dc;
+
+ // warp the fragment coordinates
+ uv.x -= 0.5; uv.x *= 1.0 + (dc.y * (0.3 * warp)); uv.x += 0.5;
+ uv.y -= 0.5; uv.y *= 1.0 + (dc.x * (0.4 * warp)); uv.y += 0.5;
+
+ // determine if we are drawing in a scanline
+ float apply = abs(sin(fragCoord.y) * 0.25 * scan);
+
+ // sample the texture
+ vec3 color = texture(iChannel0, uv).rgb;
+
+ // mix the sampled color with the scanline intensity
+ fragColor = vec4(mix(color, vec3(0.0), apply), 1.0);
+}
diff --git a/ghostty/shaders/bloom.glsl b/ghostty/shaders/bloom.glsl
new file mode 100644
index 0000000..1d20930
--- /dev/null
+++ b/ghostty/shaders/bloom.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.2;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/bloom025.glsl b/ghostty/shaders/bloom025.glsl
new file mode 100644
index 0000000..5b6afe9
--- /dev/null
+++ b/ghostty/shaders/bloom025.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.025;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/bloom050.glsl b/ghostty/shaders/bloom050.glsl
new file mode 100644
index 0000000..4eb1e22
--- /dev/null
+++ b/ghostty/shaders/bloom050.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.050;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/bloom060.glsl b/ghostty/shaders/bloom060.glsl
new file mode 100644
index 0000000..c122fae
--- /dev/null
+++ b/ghostty/shaders/bloom060.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.060;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/bloom075.glsl b/ghostty/shaders/bloom075.glsl
new file mode 100644
index 0000000..ffb81cb
--- /dev/null
+++ b/ghostty/shaders/bloom075.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.075;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/bloom1.glsl b/ghostty/shaders/bloom1.glsl
new file mode 100644
index 0000000..86ec7b8
--- /dev/null
+++ b/ghostty/shaders/bloom1.glsl
@@ -0,0 +1,52 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+// Golden spiral samples, [x, y, weight] weight is inverse of distance.
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+ };
+
+float lum(vec4 c) {
+ return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec4 color = texture(iChannel0, uv);
+
+ vec2 step = vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ vec4 c = texture(iChannel0, uv + s.xy * step);
+ float l = lum(c);
+ if (l > 0.2) {
+ color += l * s.z * c * 0.1;
+ }
+ }
+
+ fragColor = color;
+}
diff --git a/ghostty/shaders/crt.glsl b/ghostty/shaders/crt.glsl
new file mode 100644
index 0000000..31d1bec
--- /dev/null
+++ b/ghostty/shaders/crt.glsl
@@ -0,0 +1,310 @@
+// source: https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// credits: https://github.com/qwerasd205
+//==============================================================
+//
+// [CRTS] PUBLIC DOMAIN CRT-STYLED SCALAR by Timothy Lottes
+//
+// [+] Adapted with alterations for use in Ghostty by Qwerasd.
+// For more information on changes, see comment below license.
+//
+//==============================================================
+//
+// LICENSE = UNLICENSE (aka PUBLIC DOMAIN)
+//
+//--------------------------------------------------------------
+// This is free and unencumbered software released into the
+// public domain.
+//--------------------------------------------------------------
+// Anyone is free to copy, modify, publish, use, compile, sell,
+// or distribute this software, either in source code form or as
+// a compiled binary, for any purpose, commercial or
+// non-commercial, and by any means.
+//--------------------------------------------------------------
+// In jurisdictions that recognize copyright laws, the author or
+// authors of this software dedicate any and all copyright
+// interest in the software to the public domain. We make this
+// dedication for the benefit of the public at large and to the
+// detriment of our heirs and successors. We intend this
+// dedication to be an overt act of relinquishment in perpetuity
+// of all present and future rights to this software under
+// copyright law.
+//--------------------------------------------------------------
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+// OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//--------------------------------------------------------------
+// For more information, please refer to
+//
+//==============================================================
+
+// This shader is a modified version of the excellent
+// FixingPixelArtFast by Timothy Lottes on Shadertoy.
+//
+// The original shader can be found at:
+// https://www.shadertoy.com/view/MtSfRK
+//
+// Modifications have been made to reduce the verbosity,
+// and many of the comments have been removed / reworded.
+// Additionally, the license has been moved to the top of
+// the file, and can be read above. I (Qwerasd) choose to
+// release the modified version under the same license.
+
+// The appearance of this shader can be altered
+// by adjusting the parameters defined below.
+
+// "Scanlines" per real screen pixel.
+// e.g. SCALE 0.5 means each scanline is 2 pixels.
+// Recommended values:
+// o High DPI displays: 0.33333333
+// - Low DPI displays: 0.66666666
+#define SCALE 0.33333333
+
+// "Tube" warp
+#define CRTS_WARP 1
+
+// Darkness of vignette in corners after warping
+// 0.0 = completely black
+// 1.0 = no vignetting
+#define MIN_VIN 0.5
+
+// Try different masks
+// #define CRTS_MASK_GRILLE 1
+// #define CRTS_MASK_GRILLE_LITE 1
+// #define CRTS_MASK_NONE 1
+#define CRTS_MASK_SHADOW 1
+
+// Scanline thinness
+// 0.50 = fused scanlines
+// 0.70 = recommended default
+// 1.00 = thinner scanlines (too thin)
+#define INPUT_THIN 0.75
+
+// Horizonal scan blur
+// -3.0 = pixely
+// -2.5 = default
+// -2.0 = smooth
+// -1.0 = too blurry
+#define INPUT_BLUR -2.75
+
+// Shadow mask effect, ranges from,
+// 0.25 = large amount of mask (not recommended, too dark)
+// 0.50 = recommended default
+// 1.00 = no shadow mask
+#define INPUT_MASK 0.65
+
+float FromSrgb1(float c) {
+ return (c <= 0.04045) ? c * (1.0 / 12.92) :
+ pow(c * (1.0 / 1.055) + (0.055 / 1.055), 2.4);
+}
+vec3 FromSrgb(vec3 c) {
+ return vec3(
+ FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b));
+}
+
+vec3 CrtsFetch(vec2 uv) {
+ return FromSrgb(texture(iChannel0, uv.xy).rgb);
+}
+
+#define CrtsRcpF1(x) (1.0/(x))
+#define CrtsSatF1(x) clamp((x),0.0,1.0)
+
+float CrtsMax3F1(float a, float b, float c) {
+ return max(a, max(b, c));
+}
+
+vec2 CrtsTone(
+ float thin,
+ float mask) {
+ #ifdef CRTS_MASK_NONE
+ mask = 1.0;
+ #endif
+
+ #ifdef CRTS_MASK_GRILLE_LITE
+ // Normal R mask is {1.0,mask,mask}
+ // LITE R mask is {mask,1.0,1.0}
+ mask = 0.5 + mask * 0.5;
+ #endif
+
+ vec2 ret;
+ float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5));
+ float pMidIn = 0.18;
+ ret.x = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut);
+ ret.y = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut);
+
+ return ret;
+}
+
+vec3 CrtsMask(vec2 pos, float dark) {
+ #ifdef CRTS_MASK_GRILLE
+ vec3 m = vec3(dark, dark, dark);
+ float x = fract(pos.x * (1.0 / 3.0));
+ if (x < (1.0 / 3.0)) m.r = 1.0;
+ else if (x < (2.0 / 3.0)) m.g = 1.0;
+ else m.b = 1.0;
+ return m;
+ #endif
+
+ #ifdef CRTS_MASK_GRILLE_LITE
+ vec3 m = vec3(1.0, 1.0, 1.0);
+ float x = fract(pos.x * (1.0 / 3.0));
+ if (x < (1.0 / 3.0)) m.r = dark;
+ else if (x < (2.0 / 3.0)) m.g = dark;
+ else m.b = dark;
+ return m;
+ #endif
+
+ #ifdef CRTS_MASK_NONE
+ return vec3(1.0, 1.0, 1.0);
+ #endif
+
+ #ifdef CRTS_MASK_SHADOW
+ pos.x += pos.y * 3.0;
+ vec3 m = vec3(dark, dark, dark);
+ float x = fract(pos.x * (1.0 / 6.0));
+ if (x < (1.0 / 3.0)) m.r = 1.0;
+ else if (x < (2.0 / 3.0)) m.g = 1.0;
+ else m.b = 1.0;
+ return m;
+ #endif
+}
+
+vec3 CrtsFilter(
+ vec2 ipos,
+ vec2 inputSizeDivOutputSize,
+ vec2 halfInputSize,
+ vec2 rcpInputSize,
+ vec2 rcpOutputSize,
+ vec2 twoDivOutputSize,
+ float inputHeight,
+ vec2 warp,
+ float thin,
+ float blur,
+ float mask,
+ vec2 tone
+) {
+ // Optional apply warp
+ vec2 pos;
+ #ifdef CRTS_WARP
+ // Convert to {-1 to 1} range
+ pos = ipos * twoDivOutputSize - vec2(1.0, 1.0);
+
+ // Distort pushes image outside {-1 to 1} range
+ pos *= vec2(
+ 1.0 + (pos.y * pos.y) * warp.x,
+ 1.0 + (pos.x * pos.x) * warp.y);
+
+ // TODO: Vignette needs optimization
+ float vin = 1.0 - (
+ (1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y)));
+ vin = CrtsSatF1((-vin) * inputHeight + inputHeight);
+
+ // Leave in {0 to inputSize}
+ pos = pos * halfInputSize + halfInputSize;
+ #else
+ pos = ipos * inputSizeDivOutputSize;
+ #endif
+
+ // Snap to center of first scanline
+ float y0 = floor(pos.y - 0.5) + 0.5;
+ // Snap to center of one of four pixels
+ float x0 = floor(pos.x - 1.5) + 0.5;
+
+ // Inital UV position
+ vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y);
+ // Fetch 4 nearest texels from 2 nearest scanlines
+ vec3 colA0 = CrtsFetch(p);
+ p.x += rcpInputSize.x;
+ vec3 colA1 = CrtsFetch(p);
+ p.x += rcpInputSize.x;
+ vec3 colA2 = CrtsFetch(p);
+ p.x += rcpInputSize.x;
+ vec3 colA3 = CrtsFetch(p);
+ p.y += rcpInputSize.y;
+ vec3 colB3 = CrtsFetch(p);
+ p.x -= rcpInputSize.x;
+ vec3 colB2 = CrtsFetch(p);
+ p.x -= rcpInputSize.x;
+ vec3 colB1 = CrtsFetch(p);
+ p.x -= rcpInputSize.x;
+ vec3 colB0 = CrtsFetch(p);
+
+ // Vertical filter
+ // Scanline intensity is using sine wave
+ // Easy filter window and integral used later in exposure
+ float off = pos.y - y0;
+ float pi2 = 6.28318530717958;
+ float hlf = 0.5;
+ float scanA = cos(min(0.5, off * thin) * pi2) * hlf + hlf;
+ float scanB = cos(min(0.5, (-off) * thin + thin) * pi2) * hlf + hlf;
+
+ // Horizontal kernel is simple gaussian filter
+ float off0 = pos.x - x0;
+ float off1 = off0 - 1.0;
+ float off2 = off0 - 2.0;
+ float off3 = off0 - 3.0;
+ float pix0 = exp2(blur * off0 * off0);
+ float pix1 = exp2(blur * off1 * off1);
+ float pix2 = exp2(blur * off2 * off2);
+ float pix3 = exp2(blur * off3 * off3);
+ float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3);
+
+ #ifdef CRTS_WARP
+ // Get rid of wrong pixels on edge
+ pixT *= max(MIN_VIN, vin);
+ #endif
+
+ scanA *= pixT;
+ scanB *= pixT;
+
+ // Apply horizontal and vertical filters
+ vec3 color =
+ (colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA +
+ (colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB;
+
+ // Apply phosphor mask
+ color *= CrtsMask(ipos, mask);
+
+ // Tonal control, start by protecting from /0
+ float peak = max(1.0 / (256.0 * 65536.0),
+ CrtsMax3F1(color.r, color.g, color.b));
+ // Compute the ratios of {R,G,B}
+ vec3 ratio = color * CrtsRcpF1(peak);
+ // Apply tonal curve to peak value
+ peak = peak * CrtsRcpF1(peak * tone.x + tone.y);
+ // Reconstruct color
+ return ratio * peak;
+}
+
+float ToSrgb1(float c) {
+ return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055);
+}
+vec3 ToSrgb(vec3 c) {
+ return vec3(
+ ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b));
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ float aspect = iResolution.x / iResolution.y;
+ fragColor.rgb = CrtsFilter(
+ fragCoord.xy,
+ vec2(1.0),
+ iResolution.xy * SCALE * 0.5,
+ 1.0 / (iResolution.xy * SCALE),
+ 1.0 / iResolution.xy,
+ 2.0 / iResolution.xy,
+ iResolution.y,
+ vec2(1.0 / (50.0 * aspect), 1.0 / 50.0),
+ INPUT_THIN,
+ INPUT_BLUR,
+ INPUT_MASK,
+ CrtsTone(INPUT_THIN, INPUT_MASK)
+ );
+
+ // Linear to SRGB for output.
+ fragColor.rgb = ToSrgb(fragColor.rgb);
+}
\ No newline at end of file
diff --git a/ghostty/shaders/cubes.glsl b/ghostty/shaders/cubes.glsl
new file mode 100644
index 0000000..2a09505
--- /dev/null
+++ b/ghostty/shaders/cubes.glsl
@@ -0,0 +1,114 @@
+// credits: https://github.com/rymdlego
+
+const float speed = 0.2;
+const float cube_size = 1.0;
+const float cube_brightness = 1.0;
+const float cube_rotation_speed = 2.8;
+const float camera_rotation_speed = 0.1;
+
+
+
+mat3 rotationMatrix(vec3 m,float a) {
+ m = normalize(m);
+ float c = cos(a),s=sin(a);
+ return mat3(c+(1.-c)*m.x*m.x,
+ (1.-c)*m.x*m.y-s*m.z,
+ (1.-c)*m.x*m.z+s*m.y,
+ (1.-c)*m.x*m.y+s*m.z,
+ c+(1.-c)*m.y*m.y,
+ (1.-c)*m.y*m.z-s*m.x,
+ (1.-c)*m.x*m.z-s*m.y,
+ (1.-c)*m.y*m.z+s*m.x,
+ c+(1.-c)*m.z*m.z);
+}
+
+float sphere(vec3 pos, float radius)
+{
+ return length(pos) - radius;
+}
+
+float box(vec3 pos, vec3 size)
+{
+ float t = iTime;
+ pos = pos * 0.9 * rotationMatrix(vec3(sin(t/4.0*speed)*10.,cos(t/4.0*speed)*12.,2.7), t*2.4/4.0*speed*cube_rotation_speed);
+ return length(max(abs(pos) - size, 0.0));
+}
+
+
+float distfunc(vec3 pos)
+{
+ float t = iTime;
+
+ float size = 0.45 + 0.25*abs(16.0*sin(t*speed/4.0));
+ // float size = 2.3 + 1.8*tan((t-5.4)*6.549);
+ size = cube_size * 0.16 * clamp(size, 2.0, 4.0);
+
+ //pos = pos * rotationMatrix(vec3(0.,-3.,0.7), 3.3 * mod(t/30.0, 4.0));
+ vec3 q = mod(pos, 5.0) - 2.5;
+ float obj1 = box(q, vec3(size));
+ return obj1;
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ float t = iTime;
+ vec2 screenPos = -1.0 + 2.0 * fragCoord.xy / iResolution.xy;
+ screenPos.x *= iResolution.x / iResolution.y;
+ vec3 cameraOrigin = vec3(t*1.0*speed, 0.0, 0.0);
+ // vec3 cameraOrigin = vec3(t*1.8*speed, 3.0+t*0.02*speed, 0.0);
+ vec3 cameraTarget = vec3(t*100., 0.0, 0.0);
+ cameraTarget = vec3(t*20.0,0.0,0.0) * rotationMatrix(vec3(0.0,0.0,1.0), t*speed*camera_rotation_speed);
+
+ vec3 upDirection = vec3(0.5, 1.0, 0.6);
+
+ vec3 cameraDir = normalize(cameraTarget - cameraOrigin);
+ vec3 cameraRight = normalize(cross(upDirection, cameraOrigin));
+ vec3 cameraUp = cross(cameraDir, cameraRight);
+
+ vec3 rayDir = normalize(cameraRight * screenPos.x + cameraUp * screenPos.y + cameraDir);
+
+ const int MAX_ITER = 64;
+ const float MAX_DIST = 48.0;
+ const float EPSILON = 0.001;
+
+ float totalDist = 0.0;
+ vec3 pos = cameraOrigin;
+ float dist = EPSILON;
+
+ for (int i = 0; i < MAX_ITER; i++)
+ {
+ if (dist < EPSILON || totalDist > MAX_DIST)
+ break;
+ dist = distfunc(pos);
+ totalDist += dist;
+ pos += dist*rayDir;
+ }
+
+ vec4 cubes;
+
+ if (dist < EPSILON)
+ {
+ // Lighting Code
+ vec2 eps = vec2(0.0, EPSILON);
+ vec3 normal = normalize(vec3(
+ distfunc(pos + eps.yxx) - distfunc(pos - eps.yxx),
+ distfunc(pos + eps.xyx) - distfunc(pos - eps.xyx),
+ distfunc(pos + eps.xxy) - distfunc(pos - eps.xxy)));
+ float diffuse = max(0., dot(-rayDir, normal));
+ float specular = pow(diffuse, 32.0);
+ vec3 color = vec3(diffuse + specular);
+ vec3 cubeColor = vec3(abs(screenPos),0.5+0.5*sin(t*2.0))*0.8;
+ cubeColor = mix(cubeColor.rgb, vec3(0.0,0.0,0.0), 1.0);
+ color += cubeColor;
+ cubes = vec4(color, 1.0) * vec4(1.0 - (totalDist/MAX_DIST));
+ cubes = vec4(cubes.rgb*0.02*cube_brightness, 0.1);
+ }
+ else {
+ cubes = vec4(0.0);
+ }
+
+ vec2 uv = fragCoord/iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, uv);
+ vec3 blendedColor = terminalColor.rgb + cubes.rgb;
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/cursor_blaze.glsl b/ghostty/shaders/cursor_blaze.glsl
new file mode 100644
index 0000000..9cbb57f
--- /dev/null
+++ b/ghostty/shaders/cursor_blaze.glsl
@@ -0,0 +1,117 @@
+float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
+{
+ vec2 d = abs(p - xy) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
+// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
+float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
+ vec2 e = b - a;
+ vec2 w = p - a;
+ vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ float segd = dot(p - proj, p - proj);
+ d = min(d, segd);
+
+ float c0 = step(0.0, p.y - a.y);
+ float c1 = 1.0 - step(0.0, p.y - b.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ return d;
+}
+
+float getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {
+ float s = 1.0;
+ float d = dot(p - v0, p - v0);
+
+ d = seg(p, v0, v3, s, d);
+ d = seg(p, v1, v0, s, d);
+ d = seg(p, v2, v1, s, d);
+ d = seg(p, v3, v2, s, d);
+
+ return s * sqrt(d);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+float blend(float t)
+{
+ float sqr = t * t;
+ return sqr / (2.0 * (sqr - t) + 1.0);
+}
+
+float antialising(float distance) {
+ return 1. - smoothstep(0., normalize(vec2(2., 2.), 0.).x, distance);
+}
+
+float determineStartVertexFactor(vec2 a, vec2 b) {
+ // Conditions using step
+ float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
+ float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
+
+ // If neither condition is met, return 1 (else case)
+ return 1.0 - max(condition1, condition2);
+}
+vec2 getRectangleCenter(vec4 rectangle) {
+ return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));
+}
+
+const vec4 TRAIL_COLOR = vec4(1.0, 0.725, 0.161, 1.0);
+const vec4 TRAIL_COLOR_ACCENT = vec4(1.0, 0., 0., 1.0);
+// const vec4 TRAIL_COLOR = vec4(0.482, 0.886, 1.0, 1.0);
+// const vec4 TRAIL_COLOR_ACCENT = vec4(0.0, 0.424, 1.0, 1.0);
+const vec4 CURRENT_CURSOR_COLOR = TRAIL_COLOR;
+const vec4 PREVIOUS_CURSOR_COLOR = TRAIL_COLOR;
+const float DURATION = 0.3;
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+ //Normalization for fragCoord to a space of -1 to 1;
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ //Normalization for cursor position and size;
+ //cursor xy has the postion in a space of -1 to 1;
+ //zw has the width and height
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ //When drawing a parellelogram between cursors for the trail i need to determine where to start at the top-left or top-right vertex of the cursor
+ float vertexFactor = determineStartVertexFactor(currentCursor.xy, previousCursor.xy);
+ float invertedVertexFactor = 1.0 - vertexFactor;
+
+ //Set every vertex of my parellogram
+ vec2 v0 = vec2(currentCursor.x + currentCursor.z * vertexFactor, currentCursor.y - currentCursor.w);
+ vec2 v1 = vec2(currentCursor.x + currentCursor.z * invertedVertexFactor, currentCursor.y);
+ vec2 v2 = vec2(previousCursor.x + currentCursor.z * invertedVertexFactor, previousCursor.y);
+ vec2 v3 = vec2(previousCursor.x + currentCursor.z * vertexFactor, previousCursor.y - previousCursor.w);
+
+ vec4 newColor = vec4(fragColor);
+
+ float progress = blend(clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0));
+
+ //Distance between cursors determine the total length of the parallelogram;
+ vec2 centerCC = getRectangleCenter(currentCursor);
+ vec2 centerCP = getRectangleCenter(previousCursor);
+ float lineLength = distance(centerCC, centerCP);
+ float distanceToEnd = distance(vu.xy, centerCC);
+ float alphaModifier = distanceToEnd / (lineLength * (1.0 - progress));
+
+ float sdfCursor = getSdfRectangle(vu, currentCursor.xy - (currentCursor.zw * offsetFactor), currentCursor.zw * 0.5);
+ float sdfTrail = getSdfParallelogram(vu, v0, v1, v2, v3);
+
+ newColor = mix(newColor, TRAIL_COLOR_ACCENT, 1.0 - smoothstep(sdfTrail, -0.01, 0.001));
+ newColor = mix(newColor, TRAIL_COLOR, 1.0 - smoothstep(sdfTrail, -0.01, 0.001));
+ newColor = mix(newColor, TRAIL_COLOR, antialising(sdfTrail));
+ newColor = mix(fragColor, newColor, 1.0 - alphaModifier);
+ newColor = mix(newColor, TRAIL_COLOR_ACCENT, 1.0 - smoothstep(sdfCursor, -0.000, 0.003 * (1. - progress)));
+ newColor = mix(newColor, CURRENT_CURSOR_COLOR, 1.0 - smoothstep(sdfCursor, -0.000, 0.003 * (1. - progress)));
+ fragColor = mix(newColor, fragColor, step(sdfCursor, 0.));
+}
diff --git a/ghostty/shaders/cursor_blaze_no_trail.glsl b/ghostty/shaders/cursor_blaze_no_trail.glsl
new file mode 100644
index 0000000..87c4e18
--- /dev/null
+++ b/ghostty/shaders/cursor_blaze_no_trail.glsl
@@ -0,0 +1,162 @@
+
+float sdBox(in vec2 p, in vec2 xy, in vec2 b)
+{
+ vec2 d = abs(p - xy) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// //Author: https://iquilezles.org/articles/distfunctions2d/
+float sdTrail(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3)
+{
+ float d = dot(p - v0, p - v0);
+ float s = 1.0;
+
+ // Edge from v3 to v0
+ {
+ vec2 e = v3 - v0;
+ vec2 w = p - v0;
+ vec2 b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ d = min(d, dot(b, b));
+
+ // Compute branchless boolean conditions:
+ float c0 = step(0.0, p.y - v0.y); // 1 if (p.y >= v0.y)
+ float c1 = 1.0 - step(0.0, p.y - v3.y); // 1 if (p.y < v3.y)
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x); // 1 if (e.x*w.y > e.y*w.x)
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ // If either allCond or noneCond is 1, then flip factor becomes -1.
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ }
+
+ // Edge from v0 to v1
+ {
+ vec2 e = v0 - v1;
+ vec2 w = p - v1;
+ vec2 b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ d = min(d, dot(b, b));
+
+ float c0 = step(0.0, p.y - v1.y);
+ float c1 = 1.0 - step(0.0, p.y - v0.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ }
+
+ // Edge from v1 to v2
+ {
+ vec2 e = v1 - v2;
+ vec2 w = p - v2;
+ vec2 b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ d = min(d, dot(b, b));
+
+ float c0 = step(0.0, p.y - v2.y);
+ float c1 = 1.0 - step(0.0, p.y - v1.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ }
+
+ // Edge from v2 to v3
+ {
+ vec2 e = v2 - v3;
+ vec2 w = p - v3;
+ vec2 b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ d = min(d, dot(b, b));
+
+ float c0 = step(0.0, p.y - v3.y);
+ float c1 = 1.0 - step(0.0, p.y - v2.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ }
+
+ return s * sqrt(d);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+float ParametricBlend(float t)
+{
+ float sqr = t * t;
+ return sqr / (2.0 * (sqr - t) + 1.0);
+}
+
+float antialising(float distance) {
+ return 1. - smoothstep(0., normalize(vec2(2., 2.), 0.).x, distance);
+}
+
+float determineStartVertexFactor(vec2 a, vec2 b) {
+ // Conditions using step
+ float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
+ float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
+
+ // If neither condition is met, return 1 (else case)
+ return 1.0 - max(condition1, condition2);
+}
+
+const vec4 TRAIL_COLOR = vec4(0.651, 0.545, 0.980, 1.0);
+const vec4 TRAIL_COLOR_ACCENT = vec4(0.957, 0.447, 0.714, 1.0);
+// const vec4 TRAIL_COLOR = vec4(0.482, 0.886, 1.0, 1.0);
+// const vec4 TRAIL_COLOR_ACCENT = vec4(0.0, 0.424, 1.0, 1.0);
+const vec4 CURRENT_CURSOR_COLOR = TRAIL_COLOR;
+const vec4 PREVIOUS_CURSOR_COLOR = TRAIL_COLOR;
+const float DURATION = 0.3;
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+ //Normalization for fragCoord to a space of -1 to 1;
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ //Normalization for cursor position and size;
+ //cursor xy has the postion in a space of -1 to 1;
+ //zw has the width and height
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ //When drawing a parellelogram between cursors for the trail i need to determine where to start at the top-left or top-right vertex of the cursor
+ float vertexFactor = determineStartVertexFactor(currentCursor.xy, previousCursor.xy);
+ float invertedVertexFactor = 1.0 - vertexFactor;
+
+ //Set every vertex of my parellogram
+ vec2 v0 = vec2(currentCursor.x + currentCursor.z * vertexFactor, currentCursor.y - currentCursor.w);
+ vec2 v1 = vec2(currentCursor.x + currentCursor.z * invertedVertexFactor, currentCursor.y);
+ vec2 v2 = vec2(previousCursor.x + currentCursor.z * invertedVertexFactor, previousCursor.y);
+ vec2 v3 = vec2(previousCursor.x + currentCursor.z * vertexFactor, previousCursor.y - previousCursor.w);
+
+ vec4 newColor = vec4(fragColor);
+
+ float progress = ParametricBlend(clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0));
+
+ //Distance between cursors determine the total length of the parallelogram;
+ float lineLength = distance(currentCursor.xy, previousCursor.xy);
+ float distanceToEnd = distance(vu.xy, vec2(currentCursor.x + (currentCursor.z / 2.), currentCursor.y - (currentCursor.w / 2.)));
+ float alphaModifier = distanceToEnd / (lineLength * (1.0 - progress));
+
+ // float d2 = sdTrail(vu, v0, v1, v2, v3);
+ // newColor = mix(newColor, TRAIL_COLOR_ACCENT, 1.0 - smoothstep(d2, -0.01, 0.001));
+ // newColor = mix(newColor, TRAIL_COLOR, 1.0 - smoothstep(d2, -0.01, 0.001));
+ // newColor = mix(newColor, TRAIL_COLOR, antialising(d2));
+
+ float cCursorDistance = sdBox(vu, currentCursor.xy - (currentCursor.zw * offsetFactor), currentCursor.zw * 0.5);
+ newColor = mix(newColor, TRAIL_COLOR_ACCENT, 1.0 - smoothstep(cCursorDistance, -0.000, 0.003 * (1. - progress)));
+ newColor = mix(newColor, CURRENT_CURSOR_COLOR, 1.0 - smoothstep(cCursorDistance, -0.000, 0.003 * (1. - progress)));
+
+ // float pCursorDistance = sdBox(vu, previousCursor.xy - (previousCursor.zw * offsetFactor), previousCursor.zw * 0.5);
+ // newColor = mix(newColor, PREVIOUS_CURSOR_COLOR, antialising(pCursorDistance));
+
+ fragColor = mix(fragColor, newColor, 1.);
+ // fragColor = mix(fragColor, newColor, 1.0 - alphaModifier);
+}
diff --git a/ghostty/shaders/cursor_smear.glsl b/ghostty/shaders/cursor_smear.glsl
new file mode 100644
index 0000000..63119de
--- /dev/null
+++ b/ghostty/shaders/cursor_smear.glsl
@@ -0,0 +1,117 @@
+float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
+{
+ vec2 d = abs(p - xy) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
+// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
+
+float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
+ vec2 e = b - a;
+ vec2 w = p - a;
+ vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ float segd = dot(p - proj, p - proj);
+ d = min(d, segd);
+
+ float c0 = step(0.0, p.y - a.y);
+ float c1 = 1.0 - step(0.0, p.y - b.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ return d;
+}
+
+float getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {
+ float s = 1.0;
+ float d = dot(p - v0, p - v0);
+
+ d = seg(p, v0, v3, s, d);
+ d = seg(p, v1, v0, s, d);
+ d = seg(p, v2, v1, s, d);
+ d = seg(p, v3, v2, s, d);
+
+ return s * sqrt(d);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+float antialising(float distance) {
+ return 1. - smoothstep(0., normalize(vec2(2., 2.), 0.).x, distance);
+}
+
+float determineStartVertexFactor(vec2 a, vec2 b) {
+ // Conditions using step
+ float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
+ float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
+
+ // If neither condition is met, return 1 (else case)
+ return 1.0 - max(condition1, condition2);
+}
+
+vec2 getRectangleCenter(vec4 rectangle) {
+ return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));
+}
+float ease(float x) {
+ return pow(1.0 - x, 3.0);
+}
+
+// Use this site to convert from HEX to vec4
+// https://enchanted.games/app/colour-converter/
+// const vec4 TRAIL_COLOR = vec4(1., 1., 0., 1.0); // yellow
+const vec4 TRAIL_COLOR = vec4(0.9s76, 0.302, 1.0, 1.0); // cursor
+// const vec4 TRAIL_COLOR = vec4(0.914, 0.702, 0.992, 1.0); // light cursor
+// const vec4 TRAIL_COLOR = vec4(0.016, 0.82, 0.976, 1.0); // cyan
+// const vec4 TRAIL_COLOR = vec4(0.216, 0.957, 0.6, 1.0); // green
+const float OPACITY = 0.6;
+const float DURATION = 0.10; //IN SECONDS
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+ // Normalization for fragCoord to a space of -1 to 1;
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ // Normalization for cursor position and size;
+ // cursor xy has the postion in a space of -1 to 1;
+ // zw has the width and height
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ // When drawing a parellelogram between cursors for the trail i need to determine where to start at the top-left or top-right vertex of the cursor
+ float vertexFactor = determineStartVertexFactor(currentCursor.xy, previousCursor.xy);
+ float invertedVertexFactor = 1.0 - vertexFactor;
+
+ // Set every vertex of my parellogram
+ vec2 v0 = vec2(currentCursor.x + currentCursor.z * vertexFactor, currentCursor.y - currentCursor.w);
+ vec2 v1 = vec2(currentCursor.x + currentCursor.z * invertedVertexFactor, currentCursor.y);
+ vec2 v2 = vec2(previousCursor.x + currentCursor.z * invertedVertexFactor, previousCursor.y);
+ vec2 v3 = vec2(previousCursor.x + currentCursor.z * vertexFactor, previousCursor.y - previousCursor.w);
+
+ float sdfCurrentCursor = getSdfRectangle(vu, currentCursor.xy - (currentCursor.zw * offsetFactor), currentCursor.zw * 0.5);
+ float sdfTrail = getSdfParallelogram(vu, v0, v1, v2, v3);
+
+ float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);
+ float easedProgress = ease(progress);
+ // Distance between cursors determine the total length of the parallelogram;
+ vec2 centerCC = getRectangleCenter(currentCursor);
+ vec2 centerCP = getRectangleCenter(previousCursor);
+ float lineLength = distance(centerCC, centerCP);
+
+ vec4 newColor = vec4(fragColor);
+ // Draw trail
+ newColor = mix(newColor, TRAIL_COLOR, antialising(sdfTrail));
+ // Draw current cursor
+ newColor = mix(newColor, TRAIL_COLOR, antialising(sdfCurrentCursor));
+ newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
+ // newColor = mix(fragColor, newColor, OPACITY);
+ fragColor = mix(fragColor, newColor, step(sdfCurrentCursor, easedProgress * lineLength));
+}
diff --git a/ghostty/shaders/cursor_smear_fade.glsl b/ghostty/shaders/cursor_smear_fade.glsl
new file mode 100644
index 0000000..fa8be9d
--- /dev/null
+++ b/ghostty/shaders/cursor_smear_fade.glsl
@@ -0,0 +1,114 @@
+float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
+{
+ vec2 d = abs(p - xy) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
+// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
+
+float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
+ vec2 e = b - a;
+ vec2 w = p - a;
+ vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ float segd = dot(p - proj, p - proj);
+ d = min(d, segd);
+
+ float c0 = step(0.0, p.y - a.y);
+ float c1 = 1.0 - step(0.0, p.y - b.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ return d;
+}
+
+float getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {
+ float s = 1.0;
+ float d = dot(p - v0, p - v0);
+
+ d = seg(p, v0, v3, s, d);
+ d = seg(p, v1, v0, s, d);
+ d = seg(p, v2, v1, s, d);
+ d = seg(p, v3, v2, s, d);
+
+ return s * sqrt(d);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+float antialising(float distance) {
+ return 1. - smoothstep(0., normalize(vec2(2., 2.), 0.).x, distance);
+}
+
+float determineStartVertexFactor(vec2 a, vec2 b) {
+ // Conditions using step
+ float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
+ float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
+
+ // If neither condition is met, return 1 (else case)
+ return 1.0 - max(condition1, condition2);
+}
+
+vec2 getRectangleCenter(vec4 rectangle) {
+ return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));
+}
+float ease(float x) {
+ return pow(1.0 - x, 3.0);
+}
+
+const vec4 TRAIL_COLOR = vec4(1., 1., 0., 1.0);
+const float DURATION = 0.5; //IN SECONDS
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+ // Normalization for fragCoord to a space of -1 to 1;
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ // Normalization for cursor position and size;
+ // cursor xy has the postion in a space of -1 to 1;
+ // zw has the width and height
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ // When drawing a parellelogram between cursors for the trail i need to determine where to start at the top-left or top-right vertex of the cursor
+ float vertexFactor = determineStartVertexFactor(currentCursor.xy, previousCursor.xy);
+ float invertedVertexFactor = 1.0 - vertexFactor;
+
+ // Set every vertex of my parellogram
+ vec2 v0 = vec2(currentCursor.x + currentCursor.z * vertexFactor, currentCursor.y - currentCursor.w);
+ vec2 v1 = vec2(currentCursor.x + currentCursor.z * invertedVertexFactor, currentCursor.y);
+ vec2 v2 = vec2(previousCursor.x + currentCursor.z * invertedVertexFactor, previousCursor.y);
+ vec2 v3 = vec2(previousCursor.x + currentCursor.z * vertexFactor, previousCursor.y - previousCursor.w);
+
+ float sdfCurrentCursor = getSdfRectangle(vu, currentCursor.xy - (currentCursor.zw * offsetFactor), currentCursor.zw * 0.5);
+ float sdfTrail = getSdfParallelogram(vu, v0, v1, v2, v3);
+
+ float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);
+ float easedProgress = ease(progress);
+ // Distance between cursors determine the total length of the parallelogram;
+ vec2 centerCC = getRectangleCenter(currentCursor);
+ vec2 centerCP = getRectangleCenter(previousCursor);
+ float lineLength = distance(centerCC, centerCP);
+
+ vec4 newColor = vec4(fragColor);
+ // Compute fade factor based on distance along the trail
+ float fadeFactor = 1.0 - smoothstep(lineLength, sdfCurrentCursor, easedProgress * lineLength);
+
+ // Apply fading effect to trail color
+ vec4 fadedTrailColor = TRAIL_COLOR * fadeFactor;
+
+ // Blend trail with fade effect
+ newColor = mix(newColor, fadedTrailColor, antialising(sdfTrail));
+ // Draw current cursor
+ newColor = mix(newColor, TRAIL_COLOR, antialising(sdfCurrentCursor));
+ newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
+ fragColor = mix(fragColor, newColor, step(sdfCurrentCursor, easedProgress * lineLength));
+}
diff --git a/ghostty/shaders/cursor_warp.glsl b/ghostty/shaders/cursor_warp.glsl
new file mode 100644
index 0000000..82afade
--- /dev/null
+++ b/ghostty/shaders/cursor_warp.glsl
@@ -0,0 +1,303 @@
+// --- CONFIGURATION ---
+vec4 TRAIL_COLOR = vec4(0.957, 0.447, 0.714, 1.0); // can change to eg: vec4(0.2, 0.6, 1.0, 0.5);
+const float DURATION = 0.2; // total animation time
+const float TRAIL_SIZE = 0.8; // 0.0 = all corners move together. 1.0 = max smear (leading corners jump instantly)
+const float THRESHOLD_MIN_DISTANCE = 1.5; // min distance to show trail (units of cursor height)
+const float BLUR = 1.0; // blur size in pixels (for antialiasing)
+const float TRAIL_THICKNESS = 1.0; // 1.0 = full cursor height, 0.0 = zero height, >1.0 = funky aah
+const float TRAIL_THICKNESS_X = 0.9;
+
+const float FADE_ENABLED = 0.0; // 1.0 to enable fade gradient along the trail, 0.0 to disable
+const float FADE_EXPONENT = 5.0; // exponent for fade gradient along the trail
+
+// --- CONSTANTS for easing functions ---
+const float PI = 3.14159265359;
+const float C1_BACK = 1.70158;
+const float C2_BACK = C1_BACK * 1.525;
+const float C3_BACK = C1_BACK + 1.0;
+const float C4_ELASTIC = (2.0 * PI) / 3.0;
+const float C5_ELASTIC = (2.0 * PI) / 4.5;
+const float SPRING_STIFFNESS = 9.0;
+const float SPRING_DAMPING = 0.9;
+
+// --- EASING FUNCTIONS ---
+
+// // Linear
+// float ease(float x) {
+// return x;
+// }
+
+// // EaseOutQuad
+// float ease(float x) {
+// return 1.0 - (1.0 - x) * (1.0 - x);
+// }
+
+// // EaseOutCubic
+// float ease(float x) {
+// return 1.0 - pow(1.0 - x, 3.0);
+// }
+
+// // EaseOutQuart
+// float ease(float x) {
+// return 1.0 - pow(1.0 - x, 4.0);
+// }
+
+// // EaseOutQuint
+// float ease(float x) {
+// return 1.0 - pow(1.0 - x, 5.0);
+// }
+
+// // EaseOutSine
+// float ease(float x) {
+// return sin((x * PI) / 2.0);
+// }
+
+// // EaseOutExpo
+// float ease(float x) {
+// return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x);
+// }
+
+// EaseOutCirc
+float ease(float x) {
+ return sqrt(1.0 - pow(x - 1.0, 2.0));
+}
+
+// // EaseOutBack
+// float ease(float x) {
+// return 1.0 + C3_BACK * pow(x - 1.0, 3.0) + C1_BACK * pow(x - 1.0, 2.0);
+// }
+
+// // EaseOutElastic
+// float ease(float x) {
+// return x == 0.0 ? 0.0
+// : x == 1.0 ? 1.0
+// : pow(2.0, -10.0 * x) * sin((x * 10.0 - 0.75) * C4_ELASTIC) + 1.0;
+// }
+
+// // Parametric Spring
+// float ease(float x) {
+// x = clamp(x, 0.0, 1.0);
+// float decay = exp(-SPRING_DAMPING * SPRING_STIFFNESS * x);
+// float freq = sqrt(SPRING_STIFFNESS * (1.0 - SPRING_DAMPING * SPRING_DAMPING));
+// float osc = cos(freq * 6.283185 * x) + (SPRING_DAMPING * sqrt(SPRING_STIFFNESS) / freq) * sin(freq * 6.283185 * x);
+// return 1.0 - decay * osc;
+// }
+
+float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
+{
+ vec2 d = abs(p - xy) - b;
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
+// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
+float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
+ vec2 e = b - a;
+ vec2 w = p - a;
+ vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
+ float segd = dot(p - proj, p - proj);
+ d = min(d, segd);
+
+ float c0 = step(0.0, p.y - a.y);
+ float c1 = 1.0 - step(0.0, p.y - b.y);
+ float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
+ float allCond = c0 * c1 * c2;
+ float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
+ float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
+ s *= flip;
+ return d;
+}
+
+float getSdfConvexQuad(in vec2 p, in vec2 v1, in vec2 v2, in vec2 v3, in vec2 v4) {
+ float s = 1.0;
+ float d = dot(p - v1, p - v1);
+
+ d = seg(p, v1, v2, s, d);
+ d = seg(p, v2, v3, s, d);
+ d = seg(p, v3, v4, s, d);
+ d = seg(p, v4, v1, s, d);
+
+ return s * sqrt(d);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+float antialising(float distance, float blurAmount) {
+ return 1. - smoothstep(0., normalize(vec2(blurAmount, blurAmount), 0.).x, distance);
+}
+
+// Determines animation duration based on a corner's alignment with the move direction(dot product)
+// dot_val will be in [-2, 2]
+// > 0.5 (1 or 2) = Leading
+// > -0.5 (0) = Side
+// <= -0.5 (-1 or -2) = Trailing
+float getDurationFromDot(float dot_val, float DURATION_LEAD, float DURATION_SIDE, float DURATION_TRAIL) {
+ float isLead = step(0.5, dot_val);
+ float isSide = step(-0.5, dot_val) * (1.0 - isLead);
+
+ // Start with trailing duration
+ float duration = mix(DURATION_TRAIL, DURATION_SIDE, isSide);
+ // Mix in leading duration
+ duration = mix(duration, DURATION_LEAD, isLead);
+ return duration;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord){
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+
+ // normalization & setup(-1, 1 coords)
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);
+ vec2 halfSizeCC = currentCursor.zw * 0.5;
+ vec2 centerCP = previousCursor.xy - (previousCursor.zw * offsetFactor);
+ vec2 halfSizeCP = previousCursor.zw * 0.5;
+
+ float sdfCurrentCursor = getSdfRectangle(vu, centerCC, halfSizeCC);
+
+ float lineLength = distance(centerCC, centerCP);
+ float minDist = currentCursor.w * THRESHOLD_MIN_DISTANCE;
+
+ vec4 newColor = vec4(fragColor);
+
+ float baseProgress = iTime - iTimeCursorChange;
+
+ if (lineLength > minDist && baseProgress < DURATION - 0.001) {
+ // defining corners of cursors
+
+ // Y (Height) with TRAIL_THICKNESS
+ float cc_half_height = currentCursor.w * 0.5;
+ float cc_center_y = currentCursor.y - cc_half_height;
+ float cc_new_half_height = cc_half_height * TRAIL_THICKNESS;
+ float cc_new_top_y = cc_center_y + cc_new_half_height;
+ float cc_new_bottom_y = cc_center_y - cc_new_half_height;
+
+ // X (Width) with TRAIL_THICKNESS
+ float cc_half_width = currentCursor.z * 0.5;
+ float cc_center_x = currentCursor.x + cc_half_width;
+ float cc_new_half_width = cc_half_width * TRAIL_THICKNESS_X;
+ float cc_new_left_x = cc_center_x - cc_new_half_width;
+ float cc_new_right_x = cc_center_x + cc_new_half_width;
+
+ vec2 cc_tl = vec2(cc_new_left_x, cc_new_top_y);
+ vec2 cc_tr = vec2(cc_new_right_x, cc_new_top_y);
+ vec2 cc_bl = vec2(cc_new_left_x, cc_new_bottom_y);
+ vec2 cc_br = vec2(cc_new_right_x, cc_new_bottom_y);
+
+ // same thing for previous cursor
+ float cp_half_height = previousCursor.w * 0.5;
+ float cp_center_y = previousCursor.y - cp_half_height;
+ float cp_new_half_height = cp_half_height * TRAIL_THICKNESS;
+ float cp_new_top_y = cp_center_y + cp_new_half_height;
+ float cp_new_bottom_y = cp_center_y - cp_new_half_height;
+
+ float cp_half_width = previousCursor.z * 0.5;
+ float cp_center_x = previousCursor.x + cp_half_width;
+ float cp_new_half_width = cp_half_width * TRAIL_THICKNESS_X;
+ float cp_new_left_x = cp_center_x - cp_new_half_width;
+ float cp_new_right_x = cp_center_x + cp_new_half_width;
+
+ vec2 cp_tl = vec2(cp_new_left_x, cp_new_top_y);
+ vec2 cp_tr = vec2(cp_new_right_x, cp_new_top_y);
+ vec2 cp_bl = vec2(cp_new_left_x, cp_new_bottom_y);
+ vec2 cp_br = vec2(cp_new_right_x, cp_new_bottom_y);
+
+ // calculating durations for every corner
+ const float DURATION_TRAIL = DURATION;
+ const float DURATION_LEAD = DURATION * (1.0 - TRAIL_SIZE);
+ const float DURATION_SIDE = (DURATION_LEAD + DURATION_TRAIL) / 2.0;
+
+ vec2 moveVec = centerCC - centerCP;
+ vec2 s = sign(moveVec);
+
+ // dot products for each corner, determining alignment with movement direction
+ float dot_tl = dot(vec2(-1., 1.), s);
+ float dot_tr = dot(vec2( 1., 1.), s);
+ float dot_bl = dot(vec2(-1.,-1.), s);
+ float dot_br = dot(vec2( 1.,-1.), s);
+
+ // assign durations based on dot products
+ float dur_tl = getDurationFromDot(dot_tl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+ float dur_tr = getDurationFromDot(dot_tr, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+ float dur_bl = getDurationFromDot(dot_bl, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+ float dur_br = getDurationFromDot(dot_br, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+
+ // check direction of horizontal movement
+ float isMovingRight = step(0.5, s.x);
+ float isMovingLeft = step(0.5, -s.x);
+
+ // calculate vertical-rail durations
+ float dot_right_edge = (dot_tr + dot_br) * 0.5;
+ float dur_right_rail = getDurationFromDot(dot_right_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+
+ float dot_left_edge = (dot_tl + dot_bl) * 0.5;
+ float dur_left_rail = getDurationFromDot(dot_left_edge, DURATION_LEAD, DURATION_SIDE, DURATION_TRAIL);
+
+ float final_dur_tl = mix(dur_tl, dur_left_rail, isMovingLeft);
+ float final_dur_bl = mix(dur_bl, dur_left_rail, isMovingLeft);
+
+ float final_dur_tr = mix(dur_tr, dur_right_rail, isMovingRight);
+ float final_dur_br = mix(dur_br, dur_right_rail, isMovingRight);
+
+ // calculate progress for each corner based on the duration and time since cursor change
+ float prog_tl = ease(clamp(baseProgress / final_dur_tl, 0.0, 1.0));
+ float prog_tr = ease(clamp(baseProgress / final_dur_tr, 0.0, 1.0));
+ float prog_bl = ease(clamp(baseProgress / final_dur_bl, 0.0, 1.0));
+ float prog_br = ease(clamp(baseProgress / final_dur_br, 0.0, 1.0));
+
+ // get the trial corner positions based on progress
+ vec2 v_tl = mix(cp_tl, cc_tl, prog_tl);
+ vec2 v_tr = mix(cp_tr, cc_tr, prog_tr);
+ vec2 v_br = mix(cp_br, cc_br, prog_br);
+ vec2 v_bl = mix(cp_bl, cc_bl, prog_bl);
+
+ // DRAWING THE TRAIL
+ float sdfTrail = getSdfConvexQuad(vu, v_tl, v_tr, v_br, v_bl);
+
+ // --- FADE GRADIENT CALCULATION ---
+ vec2 fragVec = vu - centerCP;
+
+ // project fragment onto movement vector, normalize to [0, 1]
+ // 0.0 at tail, 1.0 at head
+ // tiny epsilon to avoid division by zero if moveVec is (0,0)
+ float fadeProgress = clamp(dot(fragVec, moveVec) / (dot(moveVec, moveVec) + 1e-6), 0.0, 1.0);
+
+ vec4 trail = TRAIL_COLOR;
+
+ float effectiveBlur = BLUR;
+ if (BLUR < 2.5) {
+ // no antialising on horizontal/vertical movement, fixes 'pulse' like thing on end cursor
+ float isDiagonal = abs(s.x) * abs(s.y); // 1.0 if diagonal, 0.0 if H/V
+ float effectiveBlur = mix(0.0, BLUR, isDiagonal);
+ }
+ float shapeAlpha = antialising(sdfTrail, effectiveBlur); // shape mask
+
+ if (FADE_ENABLED > 0.5) {
+ // apply fade gradient along the trail
+ // float fadeStart = 0.2;
+ // float easedProgress = smoothstep(fadeStart, 1.0, fadeProgress);
+ // easedProgress = pow(2.0, 10.0 * (fadeProgress - 1.0));
+ float easedProgress = pow(fadeProgress, FADE_EXPONENT);
+ trail.a *= easedProgress;
+ }
+
+ float finalAlpha = trail.a * shapeAlpha;
+
+ // newColor.a to preserve the background alpha.
+ newColor = mix(newColor, vec4(trail.rgb, newColor.a), finalAlpha);
+
+ // punch hole on the trail, so current cursor is drawn on top
+ newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
+
+ }
+
+ fragColor = newColor;
+}
diff --git a/ghostty/shaders/dither.glsl b/ghostty/shaders/dither.glsl
new file mode 100644
index 0000000..7bfe740
--- /dev/null
+++ b/ghostty/shaders/dither.glsl
@@ -0,0 +1,30 @@
+// Simple "dithering" effect
+// (c) moni-dz (https://github.com/moni-dz)
+// CC BY-NC-SA 4.0 (https://creativecommons.org/licenses/by-nc-sa/4.0/)
+
+// Packed bayer pattern using bit manipulation
+const float bayerPattern[4] = float[4](
+ 0x0514, // Encoding 0,8,2,10
+ 0xC4E6, // Encoding 12,4,14,6
+ 0x3B19, // Encoding 3,11,1,9
+ 0xF7D5 // Encoding 15,7,13,5
+);
+
+float getBayerFromPacked(int x, int y) {
+ int idx = (x & 3) + ((y & 3) << 2);
+ return float((int(bayerPattern[y & 3]) >> ((x & 3) << 2)) & 0xF) * (1.0 / 16.0);
+}
+
+#define LEVELS 2.0 // Available color steps per channel
+#define INV_LEVELS (1.0 / LEVELS)
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = fragCoord * (1.0 / iResolution.xy);
+ vec3 color = texture(iChannel0, uv).rgb;
+
+ float threshold = getBayerFromPacked(int(fragCoord.x), int(fragCoord.y));
+ vec3 dithered = floor(color * LEVELS + threshold) * INV_LEVELS;
+
+ fragColor = vec4(dithered, 1.0);
+}
diff --git a/ghostty/shaders/drunkard.glsl b/ghostty/shaders/drunkard.glsl
new file mode 100644
index 0000000..e900d4a
--- /dev/null
+++ b/ghostty/shaders/drunkard.glsl
@@ -0,0 +1,68 @@
+// Drunken stupor effect using fractal Brownian motion and Perlin noise
+// (c) moni-dz (https://github.com/moni-dz)
+// CC BY-NC-SA 4.0 (https://creativecommons.org/licenses/by-nc-sa/4.0/)
+
+vec2 hash2(vec2 p) {
+ uvec2 q = uvec2(floatBitsToUint(p.x), floatBitsToUint(p.y));
+ q = (q * uvec2(1597334673U, 3812015801U)) ^ (q.yx * uvec2(2798796415U, 1979697793U));
+ return vec2(q) * (1.0/float(0xffffffffU)) * 2.0 - 1.0;
+}
+
+float perlin2d(vec2 p) {
+ vec2 i = floor(p);
+ vec2 f = fract(p);
+ vec2 u = f*f*(3.0-2.0*f);
+
+ return mix(mix(dot(hash2(i + vec2(0.0,0.0)), f - vec2(0.0,0.0)),
+ dot(hash2(i + vec2(1.0,0.0)), f - vec2(1.0,0.0)), u.x),
+ mix(dot(hash2(i + vec2(0.0,1.0)), f - vec2(0.0,1.0)),
+ dot(hash2(i + vec2(1.0,1.0)), f - vec2(1.0,1.0)), u.x), u.y);
+}
+
+#define OCTAVES 10 // How many passes of fractal Brownian motion to perform
+#define GAIN 0.5 // How much should each pixel move
+#define LACUNARITY 2.0 // How fast should each ripple be per pass
+
+float fbm(vec2 p) {
+ float sum = 0.0;
+ float amp = 0.5;
+ float freq = 1.0;
+
+ for(int i = 0; i < OCTAVES; i++) {
+ sum += amp * perlin2d(p * freq);
+ freq *= LACUNARITY;
+ amp *= GAIN;
+ }
+
+ return sum;
+}
+
+
+#define NOISE_SCALE 1.0 // How distorted the image you want to be
+#define NOISE_INTENSITY 0.05 // How strong the noise effect is
+#define ABERRATION true // Chromatic aberration
+#define ABERRATION_DELTA 0.1 // How strong the chromatic aberration effect is
+#define ANIMATE true
+#define SPEED 0.4 // Animation speed
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = fragCoord/iResolution.xy;
+ float time = ANIMATE ? iTime * SPEED : 0.0;
+
+ vec2 noisePos = uv * NOISE_SCALE + vec2(time);
+ float noise = fbm(noisePos) * NOISE_INTENSITY;
+
+ vec3 col;
+
+ if (ABERRATION) {
+ col.r = texture(iChannel0, uv + vec2(noise * (1.0 + ABERRATION_DELTA))).r;
+ col.g = texture(iChannel0, uv + vec2(noise)).g;
+ col.b = texture(iChannel0, uv + vec2(noise * (1.0 - ABERRATION_DELTA))).b;
+ } else {
+ vec2 distortedUV = uv + vec2(noise);
+ col = texture(iChannel0, distortedUV).rgb;
+ }
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/ghostty/shaders/fireworks-rockets.glsl b/ghostty/shaders/fireworks-rockets.glsl
new file mode 100644
index 0000000..e2f0b5a
--- /dev/null
+++ b/ghostty/shaders/fireworks-rockets.glsl
@@ -0,0 +1,109 @@
+// This Ghostty shader is a lightly modified port of https://www.shadertoy.com/view/4dBGRw
+
+#define BLACK_BLEND_THRESHOLD .4
+
+//Creates a diagonal red-and-white striped pattern.
+vec3 barberpole(vec2 pos, vec2 rocketpos) {
+ float d = (pos.x - rocketpos.x) + (pos.y - rocketpos.y);
+ vec3 col = vec3(1.0);
+
+ d = mod(d * 20., 2.0);
+ if (d > 1.0) {
+ col = vec3(1.0, 0.0, 0.0);
+ }
+
+ return col;
+}
+
+vec3 rocket(vec2 pos, vec2 rocketpos) {
+ vec3 col = vec3(0.0);
+ float f = 0.;
+ float absx = abs(rocketpos.x - pos.x);
+ float absy = abs(rocketpos.y - pos.y);
+
+ // Wooden stick
+ if (absx < 0.01 && absy < 0.22) {
+ col = vec3(1.0, 0.5, 0.5);
+ }
+
+ // Barberpole
+ if (absx < 0.05 && absy < 0.15) {
+ col = barberpole(pos, rocketpos);
+ }
+
+ // Rocket Point
+ float pointw = (rocketpos.y - pos.y - 0.25) * -0.7;
+ if ((rocketpos.y - pos.y) > 0.1) {
+ f = smoothstep(pointw - 0.001, pointw + 0.001, absx);
+
+ col = mix(vec3(1.0, 0.0, 0.0), col, f);
+ }
+
+ // Shadow
+ f = -.5 + smoothstep(-0.05, 0.05, (rocketpos.x - pos.x));
+ col *= 0.7 + f;
+
+ return col;
+}
+
+float rand(float val, float seed) {
+ return cos(val * sin(val * seed) * seed);
+}
+
+float distance2(in vec2 a, in vec2 b) {
+ return dot(a - b, a - b);
+}
+
+mat2 rr = mat2(cos(1.0), -sin(1.0), sin(1.0), cos(1.0));
+
+vec3 drawParticles(vec2 pos, vec3 particolor, float time, vec2 cpos, float gravity, float seed, float timelength) {
+ vec3 col = vec3(0.0);
+ vec2 pp = vec2(1.0, 0.0);
+ for (float i = 1.0; i <= 128.0; i++) {
+ float d = rand(i, seed);
+ float fade = (i / 128.0) * time;
+ vec2 particpos = cpos + time * pp * d;
+ pp = rr * pp;
+ col = mix(particolor / fade, col, smoothstep(0.0, 0.0001, distance2(particpos, pos)));
+ }
+ col *= smoothstep(0.0, 1.0, (timelength - time) / timelength);
+
+ return col;
+}
+vec3 drawFireworks(float time, vec2 uv, vec3 particolor, float seed) {
+ float timeoffset = 2.0;
+ vec3 col = vec3(0.0);
+ if (time <= 0.) {
+ return col;
+ }
+ if (mod(time, 6.0) > timeoffset) {
+ col = drawParticles(uv, particolor, mod(time, 6.0) - timeoffset, vec2(rand(ceil(time / 6.0), seed), -0.5), 0.5, ceil(time / 6.0), seed);
+ } else {
+ col = rocket(uv * 3., vec2(3. * rand(ceil(time / 6.0), seed), 3. * (-0.5 + (timeoffset - mod(time, 6.0)))));
+ }
+ return col;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = 1.0 - 2.0 * fragCoord.xy / iResolution.xy;
+ uv.x *= iResolution.x / iResolution.y;
+ vec3 col = vec3(0.1, 0.1, 0.2);
+
+ // Flip the y-axis so that the rocket is drawn from the bottom of the screen
+ uv.y = -uv.y;
+
+ col += 0.1 * uv.y;
+
+ col += drawFireworks(iTime, uv, vec3(1.0, 0.1, 0.1), 1.);
+ col += drawFireworks(iTime - 2.0, uv, vec3(0.0, 1.0, 0.5), 2.);
+ col += drawFireworks(iTime - 4.0, uv, vec3(1.0, 1.0, 0.1), 3.);
+
+ vec2 termUV = fragCoord.xy / iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, termUV);
+
+ float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
+ vec3 blendedColor = mix(terminalColor.rgb * 1.0, col.rgb * 0.3, alpha);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/fireworks.glsl b/ghostty/shaders/fireworks.glsl
new file mode 100644
index 0000000..42bc98d
--- /dev/null
+++ b/ghostty/shaders/fireworks.glsl
@@ -0,0 +1,116 @@
+// This Ghostty shader is a port of https://www.shadertoy.com/view/lscGRl
+
+// "Fireworks" by Martijn Steinrucken aka BigWings - 2015
+// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+// Email:countfrolic@gmail.com Twitter:@The_ArtOfCode
+
+#define BLACK_BLEND_THRESHOLD .4
+#define PI 3.141592653589793238
+#define TWOPI 6.283185307179586
+#define S(x,y,z) smoothstep(x,y,z)
+#define B(x,y,z,w) S(x-z, x+z, w)*S(y+z, y-z, w)
+#define saturate(x) clamp(x,0.,1.)
+
+#define NUM_EXPLOSIONS 3.
+#define NUM_PARTICLES 42.
+
+// Noise functions by Dave Hoskins
+#define MOD3 vec3(.1031,.11369,.13787)
+vec3 hash31(float p) {
+ vec3 p3 = fract(vec3(p) * MOD3);
+ p3 += dot(p3, p3.yzx + 19.19);
+ return fract(vec3((p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y, (p3.y + p3.z) * p3.x));
+}
+float hash12(vec2 p) {
+ vec3 p3 = fract(vec3(p.xyx) * MOD3);
+ p3 += dot(p3, p3.yzx + 19.19);
+ return fract((p3.x + p3.y) * p3.z);
+}
+
+float circ(vec2 uv, vec2 pos, float size) {
+ uv -= pos;
+
+ size *= size;
+ return S(size * 1.1, size, dot(uv, uv));
+}
+
+float light(vec2 uv, vec2 pos, float size) {
+ uv -= pos;
+
+ size *= size;
+ return size / dot(uv, uv);
+}
+
+vec3 explosion(vec2 uv, vec2 p, float seed, float t) {
+ vec3 col = vec3(0.);
+
+ vec3 en = hash31(seed);
+ vec3 baseCol = en;
+ for (float i = 0.; i < NUM_PARTICLES; i++) {
+ vec3 n = hash31(i) - .5;
+
+ vec2 startP = p - vec2(0., t * t * .1);
+ vec2 endP = startP + normalize(n.xy) * n.z - vec2(0., t * .2);
+
+ float pt = 1. - pow(t - 1., 2.);
+ vec2 pos = mix(p, endP, pt);
+ float size = mix(.01, .005, S(0., .1, pt));
+ size *= S(1., .1, pt);
+
+ float sparkle = (sin((pt + n.z) * 21.) * .5 + .5);
+ sparkle = pow(sparkle, pow(en.x, 3.) * 50.) * mix(0.01, .01, en.y * n.y);
+
+ //size += sparkle*B(.6, 1., .1, t);
+ size += sparkle * B(en.x, en.y, en.z, t);
+
+ col += baseCol * light(uv, pos, size);
+ }
+
+ return col;
+}
+
+vec3 Rainbow(vec3 c) {
+ float t = iTime;
+
+ float avg = (c.r + c.g + c.b) / 3.;
+ c = avg + (c - avg) * sin(vec3(0., .333, .666) + t);
+
+ c += sin(vec3(.4, .3, .3) * t + vec3(1.1244, 3.43215, 6.435)) * vec3(.4, .1, .5);
+
+ return c;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = fragCoord.xy / iResolution.xy;
+ uv.x -= .5;
+ uv.x *= iResolution.x / iResolution.y;
+
+ // Flip the y-axis so that the gravity is downwards
+ uv.y = -uv.y + 1.;
+
+ float n = hash12(uv + 10.);
+ float t = iTime * .5;
+
+ vec3 c = vec3(0.);
+
+ for (float i = 0.; i < NUM_EXPLOSIONS; i++) {
+ float et = t + i * 1234.45235;
+ float id = floor(et);
+ et -= id;
+
+ vec2 p = hash31(id).xy;
+ p.x -= .5;
+ p.x *= 1.6;
+ c += explosion(uv, p, id, et);
+ }
+ c = Rainbow(c);
+
+ vec2 termUV = fragCoord.xy / iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, termUV);
+
+ float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
+ vec3 blendedColor = mix(terminalColor.rgb * 1.0, c.rgb * 0.3, alpha);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/gears-and-belts.glsl b/ghostty/shaders/gears-and-belts.glsl
new file mode 100644
index 0000000..9976b34
--- /dev/null
+++ b/ghostty/shaders/gears-and-belts.glsl
@@ -0,0 +1,377 @@
+// sligltly modified version of https://www.shadertoy.com/view/DsVSDV
+// The only changes are done in the mainImage function
+// Ive added comments on what to modify
+// works really well with most colorschemes
+
+#define Rot(a) mat2(cos(a),-sin(a),sin(a),cos(a))
+#define antialiasing(n) n/min(iResolution.y,iResolution.x)
+#define S(d,b) smoothstep(antialiasing(3.0),b,d)
+#define B(p,s) max(abs(p).x-s.x,abs(p).y-s.y)
+#define deg45 .707
+#define R45(p) (( p + vec2(p.y,-p.x) ) *deg45)
+#define Tri(p,s) max(R45(p).x,max(R45(p).y,B(p,s)))
+#define DF(a,b) length(a) * cos( mod( atan(a.y,a.x)+6.28/(b*8.0), 6.28/((b*8.0)*0.5))+(b-1.)*6.28/(b*8.0) + vec2(0,11) )
+
+float random (vec2 p) {
+ return fract(sin(dot(p.xy, vec2(12.9898,78.233)))* 43758.5453123);
+}
+
+float innerGear(vec2 p, float dir){
+ p*=Rot(radians(-iTime*45.+45.)*dir);
+ vec2 prevP = p;
+
+ //p*=Rot(radians(iTime*45.+20.));
+ p = DF(p,7.);
+ p-=vec2(0.24);
+ p*=Rot(deg45);
+ float d = B(p,vec2(0.01,0.06));
+ p = prevP;
+ float d2 = abs(length(p)-0.42)-0.02;
+ d = min(d,d2);
+ d2 = abs(length(p)-0.578)-0.02;
+ d = min(d,d2);
+ d2 = abs(length(p)-0.499)-0.005;
+ d = min(d,d2);
+
+ p = DF(p,7.);
+ p-=vec2(0.43);
+ p*=Rot(deg45);
+ d2 = B(p,vec2(0.01,0.04));
+ d = min(d,d2);
+
+ return d;
+}
+
+vec3 pattern1(vec2 p, vec3 col, float dir){
+ vec2 prevP = p;
+ float size = 0.499;
+ float thick = 0.15;
+
+ p+=vec2(size);
+ float d = abs(length(p)-size)-thick;
+ d = max(d,innerGear(p,dir));
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ p = prevP;
+ p-=vec2(size);
+ d = abs(length(p)-size)-thick;
+ d = max(d,innerGear(p,dir));
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ return col;
+}
+
+vec3 pattern2(vec2 p, vec3 col, float dir){
+
+ vec2 prevP = p;
+ float size = 0.33;
+ float thick = 0.15;
+ float thift = 0.0;
+ float speed = 0.3;
+
+ p-=vec2(size,0.);
+ float d = B(p,vec2(size,thick));
+
+ p.x+=thift;
+ p.x-=iTime*speed*dir;
+ p.x=mod(p.x,0.08)-0.04;
+ d = max(d,B(p,vec2(0.011,thick)));
+ p = prevP;
+ d = max(-(abs(p.y)-0.1),d);
+ //d = min(B(p,vec2(1.,0.1)),d);
+ p.y=abs(p.y)-0.079;
+ d = min(B(p,vec2(1.,0.02)),d);
+
+ p = prevP;
+ p-=vec2(0.0,size);
+ float d2 = B(p,vec2(thick,size));
+
+ p.y+=thift;
+ p.y+=iTime*speed*dir;
+ p.y=mod(p.y,0.08)-0.04;
+ d2 = max(d2,B(p,vec2(thick,0.011)));
+
+ p = prevP;
+ d2 = max(-(abs(p.x)-0.1),d2);
+ d2 = min(B(p,vec2(0.005,1.)),d2);
+ p.x=abs(p.x)-0.079;
+ d2 = min(B(p,vec2(0.02,1.)),d2);
+
+ d = min(d,d2);
+
+ p = prevP;
+ p+=vec2(0.0,size);
+ d2 = B(p,vec2(thick,size));
+
+ p.y+=thift;
+ p.y-=iTime*speed*dir;
+ p.y=mod(p.y,0.08)-0.04;
+ d2 = max(d2,B(p,vec2(thick,0.011)));
+
+ p = prevP;
+ d2 = max(-(abs(p.x)-0.1),d2);
+ d2 = min(B(p,vec2(0.005,1.)),d2);
+ p.x=abs(p.x)-0.079;
+ d2 = min(B(p,vec2(0.02,1.)),d2);
+
+ d = min(d,d2);
+
+ p = prevP;
+ p+=vec2(size,0.0);
+ d2 = B(p,vec2(size,thick));
+
+ p.x+=thift;
+ p.x+=iTime*speed*dir;
+ p.x=mod(p.x,0.08)-0.04;
+ d2 = max(d2,B(p,vec2(0.011,thick)));
+ d = min(d,d2);
+ p = prevP;
+ d = max(-(abs(p.y)-0.1),d);
+ d = min(B(p,vec2(1.,0.005)),d);
+ p.y=abs(p.y)-0.079;
+ d = min(B(p,vec2(1.,0.02)),d);
+
+ p = prevP;
+ d2 = abs(B(p,vec2(size*0.3)))-0.05;
+ d = min(d,d2);
+
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ d = B(p,vec2(0.08));
+ col = mix(col,vec3(0.),S(d,0.0));
+
+ p*=Rot(radians(60.*iTime*dir));
+ d = B(p,vec2(0.03));
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ return col;
+}
+
+vec3 drawBelt(vec2 p, vec3 col, float size){
+ vec2 prevP = p;
+
+ p*=size;
+ vec2 id = floor(p);
+ vec2 gr = fract(p)-0.5;
+ float dir = mod(id.x+id.y,2.)*2.-1.;
+ float n = random(id);
+
+ if(n<0.5){
+ if(n<0.25){
+ gr.x*=-1.;
+ }
+ col = pattern1(gr,col,dir);
+ } else {
+ if(n>0.75){
+ gr.x*=-1.;
+ }
+ col = pattern2(gr,col,dir);
+ }
+
+ return col;
+}
+
+vec3 gear(vec2 p, vec3 col, float dir){
+ vec2 prevP = p;
+
+ p*=Rot(radians(iTime*45.+13.)*-dir);
+ p = DF(p,7.);
+ p-=vec2(0.23);
+ p*=Rot(deg45);
+ float d = B(p,vec2(0.01,0.04));
+ p = prevP;
+ float d2 = abs(length(p)-0.29)-0.02;
+ d = min(d,d2);
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ p*=Rot(radians(iTime*30.-30.)*dir);
+ p = DF(p,6.);
+ p-=vec2(0.14);
+ p*=Rot(radians(45.));
+ d = B(p,vec2(0.01,0.03));
+ p = prevP;
+ d2 =abs( length(p)-0.1)-0.02;
+ p*=Rot(radians(iTime*25.+30.)*-dir);
+ d2 = max(-(abs(p.x)-0.05),d2);
+ d = min(d,d2);
+ col = mix(col,vec3(1.),S(d,0.0));
+
+ return col;
+}
+
+vec3 item0(vec2 p, vec3 col, float dir){
+ vec2 prevP = p;
+ p.x*=dir;
+ p*=Rot(radians(iTime*30.+30.));
+ float d = abs(length(p)-0.2)-0.05;
+ col = mix(col,vec3(0.3),S(d,0.0));
+
+ d = abs(length(p)-0.2)-0.05;
+ d = max(-p.x,d);
+ float a = clamp(atan(p.x,p.y)*0.5,0.3,1.);
+
+ col = mix(col,vec3(a),S(d,0.0));
+
+ return col;
+}
+
+
+vec3 item1(vec2 p, vec3 col, float dir){
+ p.x*=dir;
+ vec2 prevP = p;
+ p*=Rot(radians(iTime*30.+30.));
+ float d = abs(length(p)-0.25)-0.04;
+ d = abs(max((abs(p.y)-0.15),d))-0.005;
+ float d2 = abs(length(p)-0.25)-0.01;
+ d2 = max((abs(p.y)-0.12),d2);
+ d = min(d,d2);
+
+ d2 = abs(length(p)-0.27)-0.01;
+ d2 = max(-(abs(p.y)-0.22),d2);
+ d = min(d,d2);
+ d2 = B(p,vec2(0.01,0.32));
+ d2 = max(-(abs(p.y)-0.22),d2);
+ d = min(d,d2);
+
+ p = prevP;
+ p*=Rot(radians(iTime*-20.+30.));
+ p = DF(p,2.);
+ p-=vec2(0.105);
+ p*=Rot(radians(45.));
+ d2 = B(p,vec2(0.03,0.01));
+ d = min(d,d2);
+
+ p = prevP;
+ d2 = abs(length(p)-0.09)-0.005;
+ d2 = max(-(abs(p.x)-0.03),d2);
+ d2 = max(-(abs(p.y)-0.03),d2);
+ d = min(d,d2);
+
+ col = mix(col,vec3(0.6),S(d,0.0));
+
+ return col;
+}
+
+vec3 item2(vec2 p, vec3 col, float dir){
+ p.x*=dir;
+ p*=Rot(radians(iTime*50.-10.));
+ vec2 prevP = p;
+ float d = abs(length(p)-0.15)-0.005;
+ float d2 = abs(length(p)-0.2)-0.01;
+ d2 = max((abs(p.y)-0.15),d2);
+ d = min(d,d2);
+
+ p = DF(p,1.);
+ p-=vec2(0.13);
+ p*=Rot(radians(45.));
+ d2 = B(p,vec2(0.008,0.1));
+ d = min(d,d2);
+
+ p = prevP;
+ p = DF(p,4.);
+ p-=vec2(0.18);
+ p*=Rot(radians(45.));
+ d2 = B(p,vec2(0.005,0.02));
+ d = min(d,d2);
+
+ col = mix(col,vec3(0.6),S(d,0.0));
+
+ return col;
+}
+
+float needle(vec2 p){
+ p.y-=0.05;
+ p*=1.5;
+ vec2 prevP = p;
+ p.y-=0.3;
+ p.x*=6.;
+ float d = Tri(p,vec2(0.3));
+ p = prevP;
+ p.y+=0.1;
+ p.x*=2.;
+ p.y*=-1.;
+ float d2 = Tri(p,vec2(0.1));
+ d = min(d,d2);
+ return d;
+}
+
+vec3 item3(vec2 p, vec3 col, float dir){
+
+ p*=Rot(radians(sin(iTime*dir)*120.));
+ vec2 prevP = p;
+
+ p.y= abs(p.y)-0.05;
+ float d = needle(p);
+ p = prevP;
+ float d2 = abs(length(p)-0.1)-0.003;
+ d2 = max(-(abs(p.x)-0.05),d2);
+ d = min(d,d2);
+ d2 = abs(length(p)-0.2)-0.005;
+ d2 = max(-(abs(p.x)-0.08),d2);
+ d = min(d,d2);
+
+ p = DF(p,4.);
+ p-=vec2(0.18);
+ d2 = length(p)-0.01;
+ p = prevP;
+ d2 = max(-(abs(p.x)-0.03),d2);
+ d = min(d,d2);
+
+ col = mix(col,vec3(0.6),S(d,0.0));
+
+ return col;
+}
+
+vec3 drawGearsAndItems(vec2 p, vec3 col, float size){
+ vec2 prevP = p;
+ p*=size;
+ p+=vec2(0.5);
+
+ vec2 id = floor(p);
+ vec2 gr = fract(p)-0.5;
+
+ float n = random(id);
+ float dir = mod(id.x+id.y,2.)*2.-1.;
+ if(n<0.3){
+ col = gear(gr,col,dir);
+ } else if(n>=0.3 && n<0.5){
+ col = item0(gr,col,dir);
+ } else if(n>=0.5 && n<0.7){
+ col = item1(gr,col,dir);
+ } else if(n>=0.7 && n<0.8) {
+ col = item2(gr,col,dir);
+ } else if(n>=0.8){
+ col = item3(gr,col,dir);
+ }
+
+ return col;
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ vec2 p = (fragCoord-0.5*iResolution.xy)/iResolution.y;
+ // set speed of downwards motion
+ p.y+=iTime*0.02;
+
+ float size = 4.;
+ vec3 col = vec3(0.);
+
+ // Modify the colors to be darker by multiplying with a small factor
+ vec3 darkFactor = vec3(.5); // This makes everything 50% as bright
+
+ // Get the original colors but make them darker
+ col = drawBelt(p, col, size) * darkFactor;
+ col = drawGearsAndItems(p, col, size) * darkFactor;
+
+ // Additional option: you can add a color tint to make it less stark white
+ vec3 tint = vec3(0.1, 0.12, 0.15); // Slight blue-ish dark tint
+ col = col * tint;
+
+ vec2 uv = fragCoord/iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Blend with reduced opacity for the shader elements
+ vec3 blendedColor = terminalColor.rgb + col.rgb * 0.7; // Reduced blend factor
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/glitchy.glsl b/ghostty/shaders/glitchy.glsl
new file mode 100644
index 0000000..603e3ec
--- /dev/null
+++ b/ghostty/shaders/glitchy.glsl
@@ -0,0 +1,117 @@
+// modified version of https://www.shadertoy.com/view/wld3WN
+// amount of seconds for which the glitch loop occurs
+#define DURATION 10.
+// percentage of the duration for which the glitch is triggered
+#define AMT .1
+
+#define SS(a, b, x) (smoothstep(a, b, x) * smoothstep(b, a, x))
+
+#define UI0 1597334673U
+#define UI1 3812015801U
+#define UI2 uvec2(UI0, UI1)
+#define UI3 uvec3(UI0, UI1, 2798796415U)
+#define UIF (1. / float(0xffffffffU))
+
+// Hash by David_Hoskins
+vec3 hash33(vec3 p)
+{
+ uvec3 q = uvec3(ivec3(p)) * UI3;
+ q = (q.x ^ q.y ^ q.z)*UI3;
+ return -1. + 2. * vec3(q) * UIF;
+}
+
+// Gradient noise by iq
+float gnoise(vec3 x)
+{
+ // grid
+ vec3 p = floor(x);
+ vec3 w = fract(x);
+
+ // quintic interpolant
+ vec3 u = w * w * w * (w * (w * 6. - 15.) + 10.);
+
+ // gradients
+ vec3 ga = hash33(p + vec3(0., 0., 0.));
+ vec3 gb = hash33(p + vec3(1., 0., 0.));
+ vec3 gc = hash33(p + vec3(0., 1., 0.));
+ vec3 gd = hash33(p + vec3(1., 1., 0.));
+ vec3 ge = hash33(p + vec3(0., 0., 1.));
+ vec3 gf = hash33(p + vec3(1., 0., 1.));
+ vec3 gg = hash33(p + vec3(0., 1., 1.));
+ vec3 gh = hash33(p + vec3(1., 1., 1.));
+
+ // projections
+ float va = dot(ga, w - vec3(0., 0., 0.));
+ float vb = dot(gb, w - vec3(1., 0., 0.));
+ float vc = dot(gc, w - vec3(0., 1., 0.));
+ float vd = dot(gd, w - vec3(1., 1., 0.));
+ float ve = dot(ge, w - vec3(0., 0., 1.));
+ float vf = dot(gf, w - vec3(1., 0., 1.));
+ float vg = dot(gg, w - vec3(0., 1., 1.));
+ float vh = dot(gh, w - vec3(1., 1., 1.));
+
+ // interpolation
+ float gNoise = va + u.x * (vb - va) +
+ u.y * (vc - va) +
+ u.z * (ve - va) +
+ u.x * u.y * (va - vb - vc + vd) +
+ u.y * u.z * (va - vc - ve + vg) +
+ u.z * u.x * (va - vb - ve + vf) +
+ u.x * u.y * u.z * (-va + vb + vc - vd + ve - vf - vg + vh);
+
+ return 2. * gNoise;
+}
+
+// gradient noise in range [0, 1]
+float gnoise01(vec3 x)
+{
+ return .5 + .5 * gnoise(x);
+}
+
+// warp uvs for the crt effect
+vec2 crt(vec2 uv)
+{
+ float tht = atan(uv.y, uv.x);
+ float r = length(uv);
+ // curve without distorting the center
+ r /= (1. - .1 * r * r);
+ uv.x = r * cos(tht);
+ uv.y = r * sin(tht);
+ return .5 * (uv + 1.);
+}
+
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ vec2 uv = fragCoord / iResolution.xy;
+ float t = iTime;
+
+ // smoothed interval for which the glitch gets triggered
+ float glitchAmount = SS(DURATION * .001, DURATION * AMT, mod(t, DURATION));
+ float displayNoise = 0.;
+ vec3 col = vec3(0.);
+ vec2 eps = vec2(5. / iResolution.x, 0.);
+ vec2 st = vec2(0.);
+
+ // analog distortion
+ float y = uv.y * iResolution.y;
+ float distortion = gnoise(vec3(0., y * .01, t * 500.)) * (glitchAmount * 4. + .1);
+ distortion *= gnoise(vec3(0., y * .02, t * 250.)) * (glitchAmount * 2. + .025);
+
+ ++displayNoise;
+ distortion += smoothstep(.999, 1., sin((uv.y + t * 1.6) * 2.)) * .02;
+ distortion -= smoothstep(.999, 1., sin((uv.y + t) * 2.)) * .02;
+ st = uv + vec2(distortion, 0.);
+ // chromatic aberration
+ col.r += textureLod(iChannel0, st + eps + distortion, 0.).r;
+ col.g += textureLod(iChannel0, st, 0.).g;
+ col.b += textureLod(iChannel0, st - eps - distortion, 0.).b;
+
+ // white noise + scanlines
+ displayNoise = 0.2 * clamp(displayNoise, 0., 1.);
+ col += (.15 + .65 * glitchAmount) * (hash33(vec3(fragCoord, mod(float(iFrame),
+ 1000.))).r) * displayNoise;
+ col -= (.25 + .75 * glitchAmount) * (sin(4. * t + uv.y * iResolution.y * 1.75))
+ * displayNoise;
+ fragColor = vec4(col, 1.0);
+}
diff --git a/ghostty/shaders/glow-rgbsplit-twitchy.glsl b/ghostty/shaders/glow-rgbsplit-twitchy.glsl
new file mode 100644
index 0000000..9411e4e
--- /dev/null
+++ b/ghostty/shaders/glow-rgbsplit-twitchy.glsl
@@ -0,0 +1,144 @@
+// First it does a "chromatic aberration" by splitting the rgb signals by a product of sin functions
+// over time, then it does a glow effect in a perceptual color space
+// Based on kalgynirae's Ghostty passable glow shader and NickWest's Chromatic Aberration shader demo
+// Passable glow: https://github.com/kalgynirae/dotfiles/blob/main/ghostty/glow.glsl
+// "Chromatic Aberration": https://www.shadertoy.com/view/Mds3zn
+
+// sRGB linear -> nonlinear transform from https://bottosson.github.io/posts/colorwrong/
+float f(float x) {
+ if (x >= 0.0031308) {
+ return 1.055 * pow(x, 1.0 / 2.4) - 0.055;
+ } else {
+ return 12.92 * x;
+ }
+}
+
+float f_inv(float x) {
+ if (x >= 0.04045) {
+ return pow((x + 0.055) / 1.055, 2.4);
+ } else {
+ return x / 12.92;
+ }
+}
+
+// Oklab <-> linear sRGB conversions from https://bottosson.github.io/posts/oklab/
+vec4 toOklab(vec4 rgb) {
+ vec3 c = vec3(f_inv(rgb.r), f_inv(rgb.g), f_inv(rgb.b));
+ float l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b;
+ float m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b;
+ float s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b;
+ float l_ = pow(l, 1.0 / 3.0);
+ float m_ = pow(m, 1.0 / 3.0);
+ float s_ = pow(s, 1.0 / 3.0);
+ return vec4(
+ 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
+ 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
+ 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
+ rgb.a
+ );
+}
+
+vec4 toRgb(vec4 oklab) {
+ vec3 c = oklab.rgb;
+ float l_ = c.r + 0.3963377774 * c.g + 0.2158037573 * c.b;
+ float m_ = c.r - 0.1055613458 * c.g - 0.0638541728 * c.b;
+ float s_ = c.r - 0.0894841775 * c.g - 1.2914855480 * c.b;
+ float l = l_ * l_ * l_;
+ float m = m_ * m_ * m_;
+ float s = s_ * s_ * s_;
+ vec3 linear_srgb = vec3(
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
+ -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
+ );
+ return vec4(
+ clamp(f(linear_srgb.r), 0.0, 1.0),
+ clamp(f(linear_srgb.g), 0.0, 1.0),
+ clamp(f(linear_srgb.b), 0.0, 1.0),
+ oklab.a
+ );
+}
+
+// Bloom samples from https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+const vec3[24] samples = {
+ vec3(0.1693761725038636, 0.9855514761735895, 1),
+ vec3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
+ vec3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
+ vec3(1.554155680728463, -1.2588090085709776, 0.5),
+ vec3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
+ vec3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
+ vec3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
+ vec3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
+ vec3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
+ vec3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
+ vec3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
+ vec3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
+ vec3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
+ vec3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
+ vec3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
+ vec3(-3.6548858794907493, -1.6253643308191343, 0.25),
+ vec3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
+ vec3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
+ vec3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
+ vec3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
+ vec3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
+ vec3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
+ vec3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
+ vec3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
+};
+
+float offsetFunction(float iTime) {
+ float amount = 1.0;
+ const float periods[4] = {6.0, 16.0, 19.0, 27.0};
+ for (int i = 0; i < 4; i++) {
+ amount *= 1.0 + 0.5 * sin(iTime*periods[i]);
+ }
+ //return amount;
+ return amount * periods[3];
+}
+
+const float DIM_CUTOFF = 0.35;
+const float BRIGHT_CUTOFF = 0.65;
+const float ABBERATION_FACTOR = 0.05;
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ float amount = offsetFunction(iTime);
+
+ vec3 col;
+ col.r = texture( iChannel0, vec2(uv.x-ABBERATION_FACTOR*amount / iResolution.x, uv.y) ).r;
+ col.g = texture( iChannel0, uv ).g;
+ col.b = texture( iChannel0, vec2(uv.x+ABBERATION_FACTOR*amount / iResolution.x, uv.y) ).b;
+
+ vec4 splittedColor = vec4(col, 1.0);
+ vec4 source = toOklab(splittedColor);
+ vec4 dest = source;
+
+ if (source.x > DIM_CUTOFF) {
+ dest.x *= 1.2;
+ // dest.x = 1.2;
+ } else {
+ vec2 step = vec2(1.414) / iResolution.xy;
+ vec3 glow = vec3(0.0);
+ for (int i = 0; i < 24; i++) {
+ vec3 s = samples[i];
+ float weight = s.z;
+ vec4 c = toOklab(texture(iChannel0, uv + s.xy * step));
+ if (c.x > DIM_CUTOFF) {
+ glow.yz += c.yz * weight * 0.3;
+ if (c.x <= BRIGHT_CUTOFF) {
+ glow.x += c.x * weight * 0.05;
+ } else {
+ glow.x += c.x * weight * 0.10;
+ }
+ }
+ }
+ // float lightness_diff = clamp(glow.x - dest.x, 0.0, 1.0);
+ // dest.x = lightness_diff;
+ // dest.yz = dest.yz * (1.0 - lightness_diff) + glow.yz * lightness_diff;
+ dest.xyz += glow.xyz;
+ }
+
+ fragColor = toRgb(dest);
+}
diff --git a/ghostty/shaders/gradient-background.glsl b/ghostty/shaders/gradient-background.glsl
new file mode 100644
index 0000000..beae0cf
--- /dev/null
+++ b/ghostty/shaders/gradient-background.glsl
@@ -0,0 +1,25 @@
+// credits: https://github.com/unkn0wncode
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ // Normalize pixel coordinates (range from 0 to 1)
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ // Create a gradient from bottom right to top left as a function (x + y)/2
+ float gradientFactor = (uv.x + uv.y) / 2.0;
+
+ // Define gradient colors (adjust to your preference)
+ vec3 gradientStartColor = vec3(0.1, 0.1, 0.5); // Start color (e.g., dark blue)
+ vec3 gradientEndColor = vec3(0.5, 0.1, 0.1); // End color (e.g., dark red)
+
+ vec3 gradientColor = mix(gradientStartColor, gradientEndColor, gradientFactor);
+
+ // Sample the terminal screen texture including alpha channel
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Make a mask that is 1.0 where the terminal content is not black
+ float mask = 1 - step(0.5, dot(terminalColor.rgb, vec3(1.0)));
+ vec3 blendedColor = mix(terminalColor.rgb, gradientColor, mask);
+
+ // Apply terminal's alpha to control overall opacity
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
\ No newline at end of file
diff --git a/ghostty/shaders/inside-the-matrix.glsl b/ghostty/shaders/inside-the-matrix.glsl
new file mode 100644
index 0000000..6992069
--- /dev/null
+++ b/ghostty/shaders/inside-the-matrix.glsl
@@ -0,0 +1,413 @@
+/*
+ Feel free to do anything you want with this code.
+ This shader uses "runes" code by FabriceNeyret2 (https://www.shadertoy.com/view/4ltyDM)
+ which is based on "runes" by otaviogood (https://shadertoy.com/view/MsXSRn).
+ These random runes look good as matrix symbols and have acceptable performance.
+
+ @pkazmier modified this shader to work in Ghostty.
+*/
+
+const int ITERATIONS = 40; //use less value if you need more performance
+const float SPEED = .5;
+
+const float STRIP_CHARS_MIN = 7.;
+const float STRIP_CHARS_MAX = 40.;
+const float STRIP_CHAR_HEIGHT = 0.15;
+const float STRIP_CHAR_WIDTH = 0.10;
+const float ZCELL_SIZE = 1. * (STRIP_CHAR_HEIGHT * STRIP_CHARS_MAX); //the multiplier can't be less than 1.
+const float XYCELL_SIZE = 12. * STRIP_CHAR_WIDTH; //the multiplier can't be less than 1.
+
+const int BLOCK_SIZE = 10; //in cells
+const int BLOCK_GAP = 2; //in cells
+
+const float WALK_SPEED = 0.5 * XYCELL_SIZE;
+const float BLOCKS_BEFORE_TURN = 3.;
+
+
+const float PI = 3.14159265359;
+
+
+// ---- random ----
+
+float hash(float v) {
+ return fract(sin(v)*43758.5453123);
+}
+
+float hash(vec2 v) {
+ return hash(dot(v, vec2(5.3983, 5.4427)));
+}
+
+vec2 hash2(vec2 v)
+{
+ v = vec2(v * mat2(127.1, 311.7, 269.5, 183.3));
+ return fract(sin(v)*43758.5453123);
+}
+
+vec4 hash4(vec2 v)
+{
+ vec4 p = vec4(v * mat4x2( 127.1, 311.7,
+ 269.5, 183.3,
+ 113.5, 271.9,
+ 246.1, 124.6 ));
+ return fract(sin(p)*43758.5453123);
+}
+
+vec4 hash4(vec3 v)
+{
+ vec4 p = vec4(v * mat4x3( 127.1, 311.7, 74.7,
+ 269.5, 183.3, 246.1,
+ 113.5, 271.9, 124.6,
+ 271.9, 269.5, 311.7 ) );
+ return fract(sin(p)*43758.5453123);
+}
+
+
+// ---- symbols ----
+// Slightly modified version of "runes" by FabriceNeyret2 - https://www.shadertoy.com/view/4ltyDM
+// Which is based on "runes" by otaviogood - https://shadertoy.com/view/MsXSRn
+
+float rune_line(vec2 p, vec2 a, vec2 b) { // from https://www.shadertoy.com/view/4dcfW8
+ p -= a, b -= a;
+ float h = clamp(dot(p, b) / dot(b, b), 0., 1.); // proj coord on line
+ return length(p - b * h); // dist to segment
+}
+
+float rune(vec2 U, vec2 seed, float highlight)
+{
+ float d = 1e5;
+ for (int i = 0; i < 4; i++) // number of strokes
+ {
+ vec4 pos = hash4(seed);
+ seed += 1.;
+
+ // each rune touches the edge of its box on all 4 sides
+ if (i == 0) pos.y = .0;
+ if (i == 1) pos.x = .999;
+ if (i == 2) pos.x = .0;
+ if (i == 3) pos.y = .999;
+ // snap the random line endpoints to a grid 2x3
+ vec4 snaps = vec4(2, 3, 2, 3);
+ pos = ( floor(pos * snaps) + .5) / snaps;
+
+ if (pos.xy != pos.zw) //filter out single points (when start and end are the same)
+ d = min(d, rune_line(U, pos.xy, pos.zw + .001) ); // closest line
+ }
+ return smoothstep(0.1, 0., d) + highlight*smoothstep(0.4, 0., d);
+}
+
+float random_char(vec2 outer, vec2 inner, float highlight) {
+ vec2 seed = vec2(dot(outer, vec2(269.5, 183.3)), dot(outer, vec2(113.5, 271.9)));
+ return rune(inner, seed, highlight);
+}
+
+
+// ---- digital rain ----
+
+// xy - horizontal, z - vertical
+vec3 rain(vec3 ro3, vec3 rd3, float time) {
+ vec4 result = vec4(0.);
+
+ // normalized 2d projection
+ vec2 ro2 = vec2(ro3);
+ vec2 rd2 = normalize(vec2(rd3));
+
+ // we use formulas `ro3 + rd3 * t3` and `ro2 + rd2 * t2`, `t3_to_t2` is a multiplier to convert t3 to t2
+ bool prefer_dx = abs(rd2.x) > abs(rd2.y);
+ float t3_to_t2 = prefer_dx ? rd3.x / rd2.x : rd3.y / rd2.y;
+
+ // at first, horizontal space (xy) is divided into cells (which are columns in 3D)
+ // then each xy-cell is divided into vertical cells (along z) - each of these cells contains one raindrop
+
+ ivec3 cell_side = ivec3(step(0., rd3)); //for positive rd.x use cell side with higher x (1) as the next side, for negative - with lower x (0), the same for y and z
+ ivec3 cell_shift = ivec3(sign(rd3)); //shift to move to the next cell
+
+ // move through xy-cells in the ray direction
+ float t2 = 0.; // the ray formula is: ro2 + rd2 * t2, where t2 is positive as the ray has a direction.
+ ivec2 next_cell = ivec2(floor(ro2/XYCELL_SIZE)); //first cell index where ray origin is located
+ for (int i=0; i= t2s && tmin <= t2) {
+ float u = s.x * rd2.y - s.y * rd2.x; //horizontal coord in the matrix strip
+ if (abs(u) < target_rad) {
+ u = (u/target_rad + 1.) / 2.;
+ float z = ro3.z + rd3.z * tmin/t3_to_t2;
+ float v = (z - target_z) / target_length; //vertical coord in the matrix strip
+ if (v >= 0.0 && v < 1.0) {
+ float c = floor(v * chars_count); //symbol index relative to the start of the strip, with addition of char_z_shift it becomes an index relative to the whole cell
+ float q = fract(v * chars_count);
+ vec2 char_hash = hash2(vec2(c+char_z_shift, cell_hash2.x));
+ if (char_hash.x >= 0.1 || c == 0.) { //10% of missed symbols
+ float time_factor = floor(c == 0. ? time*5.0 : //first symbol is changed fast
+ time*(1.0*cell_hash2.z + //strips are changed sometime with different speed
+ cell_hash2.w*cell_hash2.w*4.*pow(char_hash.y, 4.))); //some symbols in some strips are changed relatively often
+ float a = random_char(vec2(char_hash.x, time_factor), vec2(u,q), max(1., 3. - c/2.)*0.2); //alpha
+ a *= clamp((chars_count - 0.5 - c) / 2., 0., 1.); //tail fade
+ if (a > 0.) {
+ float attenuation = 1. + pow(0.06*tmin/t3_to_t2, 2.);
+ vec3 col = (c == 0. ? vec3(0.67, 1.0, 0.82) : vec3(0.25, 0.80, 0.40)) / attenuation;
+ float a1 = result.a;
+ result.a = a1 + (1. - a1) * a;
+ result.xyz = (result.xyz * a1 + col * (1. - a1) * a) / result.a;
+ if (result.a > 0.98) return result.xyz;
+ }
+ }
+ }
+ }
+ }
+ // not found in this cell - go to next vertical cell
+ zcell += cell_shift.z;
+ }
+ // go to next horizontal cell
+ }
+
+ return result.xyz * result.a;
+}
+
+
+// ---- main, camera ----
+
+vec2 rotate(vec2 v, float a) {
+ float s = sin(a);
+ float c = cos(a);
+ mat2 m = mat2(c, -s, s, c);
+ return m * v;
+}
+
+vec3 rotateX(vec3 v, float a) {
+ float s = sin(a);
+ float c = cos(a);
+ return mat3(1.,0.,0.,0.,c,-s,0.,s,c) * v;
+}
+
+vec3 rotateY(vec3 v, float a) {
+ float s = sin(a);
+ float c = cos(a);
+ return mat3(c,0.,-s,0.,1.,0.,s,0.,c) * v;
+}
+
+vec3 rotateZ(vec3 v, float a) {
+ float s = sin(a);
+ float c = cos(a);
+ return mat3(c,-s,0.,s,c,0.,0.,0.,1.) * v;
+}
+
+float smoothstep1(float x) {
+ return smoothstep(0., 1., x);
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ if (STRIP_CHAR_WIDTH > XYCELL_SIZE || STRIP_CHAR_HEIGHT * STRIP_CHARS_MAX > ZCELL_SIZE) {
+ // error
+ fragColor = vec4(1., 0., 0., 1.);
+ return;
+ }
+
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ float time = iTime * SPEED;
+
+ const float turn_rad = 0.25 / BLOCKS_BEFORE_TURN; //0 .. 0.5
+ const float turn_abs_time = (PI/2.*turn_rad) * 1.5; //multiplier different than 1 means a slow down on turns
+ const float turn_time = turn_abs_time / (1. - 2.*turn_rad + turn_abs_time); //0..1, but should be <= 0.5
+
+ float level1_size = float(BLOCK_SIZE) * BLOCKS_BEFORE_TURN * XYCELL_SIZE;
+ float level2_size = 4. * level1_size;
+ float gap_size = float(BLOCK_GAP) * XYCELL_SIZE;
+
+ vec3 ro = vec3(gap_size/2., gap_size/2., 0.);
+ vec3 rd = vec3(uv.x, 2.0, uv.y);
+
+ float tq = fract(time / (level2_size*4.) * WALK_SPEED); //the whole cycle time counter
+ float t8 = fract(tq*4.); //time counter while walking on one of the four big sides
+ float t1 = fract(t8*8.); //time counter while walking on one of the eight sides of the big side
+
+ vec2 prev;
+ vec2 dir;
+ if (tq < 0.25) {
+ prev = vec2(0.,0.);
+ dir = vec2(0.,1.);
+ } else if (tq < 0.5) {
+ prev = vec2(0.,1.);
+ dir = vec2(1.,0.);
+ } else if (tq < 0.75) {
+ prev = vec2(1.,1.);
+ dir = vec2(0.,-1.);
+ } else {
+ prev = vec2(1.,0.);
+ dir = vec2(-1.,0.);
+ }
+ float angle = floor(tq * 4.); //0..4 wich means 0..2*PI
+
+ prev *= 4.;
+
+ const float first_turn_look_angle = 0.4;
+ const float second_turn_drift_angle = 0.5;
+ const float fifth_turn_drift_angle = 0.25;
+
+ vec2 turn;
+ float turn_sign = 0.;
+ vec2 dirL = rotate(dir, -PI/2.);
+ vec2 dirR = -dirL;
+ float up_down = 0.;
+ float rotate_on_turns = 1.;
+ float roll_on_turns = 1.;
+ float add_angel = 0.;
+ if (t8 < 0.125) {
+ turn = dirL;
+ //dir = dir;
+ turn_sign = -1.;
+ angle -= first_turn_look_angle * (max(0., t1 - (1. - turn_time*2.)) / turn_time - max(0., t1 - (1. - turn_time)) / turn_time * 2.5);
+ roll_on_turns = 0.;
+ } else if (t8 < 0.250) {
+ prev += dir;
+ turn = dir;
+ dir = dirL;
+ angle -= 1.;
+ turn_sign = 1.;
+ add_angel += first_turn_look_angle*0.5 + (-first_turn_look_angle*0.5+1.0+second_turn_drift_angle)*t1;
+ rotate_on_turns = 0.;
+ roll_on_turns = 0.;
+ } else if (t8 < 0.375) {
+ prev += dir + dirL;
+ turn = dirR;
+ //dir = dir;
+ turn_sign = 1.;
+ add_angel += second_turn_drift_angle*sqrt(1.-t1);
+ //roll_on_turns = 0.;
+ } else if (t8 < 0.5) {
+ prev += dir + dir + dirL;
+ turn = dirR;
+ dir = dirR;
+ angle += 1.;
+ turn_sign = 0.;
+ up_down = sin(t1*PI) * 0.37;
+ } else if (t8 < 0.625) {
+ prev += dir + dir;
+ turn = dir;
+ dir = dirR;
+ angle += 1.;
+ turn_sign = -1.;
+ up_down = sin(-min(1., t1/(1.-turn_time))*PI) * 0.37;
+ } else if (t8 < 0.750) {
+ prev += dir + dir + dirR;
+ turn = dirL;
+ //dir = dir;
+ turn_sign = -1.;
+ add_angel -= (fifth_turn_drift_angle + 1.) * smoothstep1(t1);
+ rotate_on_turns = 0.;
+ roll_on_turns = 0.;
+ } else if (t8 < 0.875) {
+ prev += dir + dir + dir + dirR;
+ turn = dir;
+ dir = dirL;
+ angle -= 1.;
+ turn_sign = 1.;
+ add_angel -= fifth_turn_drift_angle - smoothstep1(t1) * (fifth_turn_drift_angle * 2. + 1.);
+ rotate_on_turns = 0.;
+ roll_on_turns = 0.;
+ } else {
+ prev += dir + dir + dir;
+ turn = dirR;
+ //dir = dir;
+ turn_sign = 1.;
+ angle += fifth_turn_drift_angle * (1.5*min(1., (1.-t1)/turn_time) - 0.5*smoothstep1(1. - min(1.,t1/(1.-turn_time))));
+ }
+
+ if (iMouse.x > 10. || iMouse.y > 10.) {
+ vec2 mouse = iMouse.xy / iResolution.xy * 2. - 1.;
+ up_down = -0.7 * mouse.y;
+ angle += mouse.x;
+ rotate_on_turns = 1.;
+ roll_on_turns = 0.;
+ } else {
+ angle += add_angel;
+ }
+
+ rd = rotateX(rd, up_down);
+
+ vec2 p;
+ if (turn_sign == 0.) {
+ // move forward
+ p = prev + dir * (turn_rad + 1. * t1);
+ }
+ else if (t1 > (1. - turn_time)) {
+ // turn
+ float tr = (t1 - (1. - turn_time)) / turn_time;
+ vec2 c = prev + dir * (1. - turn_rad) + turn * turn_rad;
+ p = c + turn_rad * rotate(dir, (tr - 1.) * turn_sign * PI/2.);
+ angle += tr * turn_sign * rotate_on_turns;
+ rd = rotateY(rd, sin(tr*turn_sign*PI) * 0.2 * roll_on_turns); //roll
+ } else {
+ // move forward
+ t1 /= (1. - turn_time);
+ p = prev + dir * (turn_rad + (1. - turn_rad*2.) * t1);
+ }
+
+ rd = rotateZ(rd, angle * PI/2.);
+
+ ro.xy += level1_size * p;
+
+ ro += rd * 0.2;
+ rd = normalize(rd);
+
+ // vec3 col = rain(ro, rd, time);
+ vec3 col = rain(ro, rd, time) * 0.25;
+
+ // Sample the terminal screen texture including alpha channel
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Combine the matrix effect with the terminal color
+ // vec3 blendedColor = terminalColor.rgb + col;
+
+ // Make a mask that is 1.0 where the terminal content is not black
+ float mask = 1.2 - step(0.5, dot(terminalColor.rgb, vec3(1.0)));
+ vec3 blendedColor = mix(terminalColor.rgb * 1.2, col, mask);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/just-snow.glsl b/ghostty/shaders/just-snow.glsl
new file mode 100644
index 0000000..c72b7fd
--- /dev/null
+++ b/ghostty/shaders/just-snow.glsl
@@ -0,0 +1,52 @@
+// Copyright (c) 2013 Andrew Baldwin (twitter: baldand, www: http://thndl.com)
+// License = Attribution-NonCommercial-ShareAlike (http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
+
+// "Just snow"
+// Simple (but not cheap) snow made from multiple parallax layers with randomly positioned
+// flakes and directions. Also includes a DoF effect. Pan around with mouse.
+
+#define LIGHT_SNOW // Comment this out for a blizzard
+
+#ifdef LIGHT_SNOW
+ #define LAYERS 50
+ #define DEPTH .5
+ #define WIDTH .3
+ #define SPEED .6
+#else // BLIZZARD
+ #define LAYERS 200
+ #define DEPTH .1
+ #define WIDTH .8
+ #define SPEED 1.5
+#endif
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ const mat3 p = mat3(13.323122,23.5112,21.71123,21.1212,28.7312,11.9312,21.8112,14.7212,61.3934);
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec3 acc = vec3(0.0);
+ float dof = 5.0 * sin(iTime * 0.1);
+ for (int i = 0; i < LAYERS; i++) {
+ float fi = float(i);
+ vec2 q =-uv*(1.0 + fi * DEPTH);
+ q += vec2(q.y * (WIDTH * mod(fi * 7.238917, 1.0) - WIDTH * 0.5), -SPEED * iTime / (1.0 + fi * DEPTH * 0.03));
+ vec3 n = vec3(floor(q), 31.189 + fi);
+ vec3 m = floor(n) * 0.00001 + fract(n);
+ vec3 mp = (31415.9 + m) / fract(p * m);
+ vec3 r = fract(mp);
+ vec2 s = abs(mod(q, 1.0) - 0.5 + 0.9 * r.xy - 0.45);
+ s += 0.01 * abs(2.0 * fract(10.0 * q.yx) - 1.0);
+ float d = 0.6 * max(s.x - s.y, s.x + s.y) + max(s.x, s.y) - 0.01;
+ float edge = 0.005 + 0.05 * min(0.5 * abs(fi - 5.0 - dof), 1.0);
+ acc += vec3(smoothstep(edge, -edge, d) * (r.x / (1.0 + 0.02 * fi * DEPTH)));
+ }
+
+ // Sample the terminal screen texture including alpha channel
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Combine the snow effect with the terminal color
+ vec3 blendedColor = terminalColor.rgb + acc;
+
+ // Use the terminal's original alpha
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/matrix-hallway.glsl b/ghostty/shaders/matrix-hallway.glsl
new file mode 100644
index 0000000..2bbee86
--- /dev/null
+++ b/ghostty/shaders/matrix-hallway.glsl
@@ -0,0 +1,40 @@
+// based on the following Shader Toy entry
+//
+// [SH17A] Matrix rain. Created by Reinder Nijhoff 2017
+// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
+// @reindernijhoff
+//
+// https://www.shadertoy.com/view/ldjBW1
+//
+
+#define SPEED_MULTIPLIER 1.
+#define GREEN_ALPHA .33
+
+#define BLACK_BLEND_THRESHOLD .4
+
+#define R fract(1e2 * sin(p.x * 8. + p.y))
+
+void mainImage(out vec4 fragColor, vec2 fragCoord) {
+ vec3 v = vec3(fragCoord, 1) / iResolution - .5;
+ // vec3 s = .5 / abs(v);
+ // scale?
+ vec3 s = .9 / abs(v);
+ s.z = min(s.y, s.x);
+ vec3 i = ceil( 8e2 * s.z * ( s.y < s.x ? v.xzz : v.zyz ) ) * .1;
+ vec3 j = fract(i);
+ i -= j;
+ vec3 p = vec3(9, int(iTime * SPEED_MULTIPLIER * (9. + 8. * sin(i).x)), 0) + i;
+ vec3 col = fragColor.rgb;
+ col.g = R / s.z;
+ p *= j;
+ col *= (R >.5 && j.x < .6 && j.y < .8) ? GREEN_ALPHA : 0.;
+
+ // Sample the terminal screen texture including alpha channel
+ vec2 uv = fragCoord.xy / iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
+ vec3 blendedColor = mix(terminalColor.rgb * 1.2, col, alpha);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/negative.glsl b/ghostty/shaders/negative.glsl
new file mode 100644
index 0000000..48101f6
--- /dev/null
+++ b/ghostty/shaders/negative.glsl
@@ -0,0 +1,8 @@
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ vec2 uv = fragCoord/iResolution.xy;
+ vec4 color = texture(iChannel0, uv);
+ fragColor = vec4(1.0 - color.x, 1.0 - color.y, 1.0 - color.z, color.w);
+}
+
diff --git a/ghostty/shaders/retro-terminal.glsl b/ghostty/shaders/retro-terminal.glsl
new file mode 100644
index 0000000..c5f315a
--- /dev/null
+++ b/ghostty/shaders/retro-terminal.glsl
@@ -0,0 +1,34 @@
+// Original shader collected from: https://www.shadertoy.com/view/WsVSzV
+// Licensed under Shadertoy's default since the original creator didn't provide any license. (CC BY NC SA 3.0)
+// Slight modifications were made to give a green-ish effect.
+
+float warp = 0.25; // simulate curvature of CRT monitor
+float scan = 0.50; // simulate darkness between scanlines
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ // squared distance from center
+ vec2 uv = fragCoord / iResolution.xy;
+ vec2 dc = abs(0.5 - uv);
+ dc *= dc;
+
+ // warp the fragment coordinates
+ uv.x -= 0.5; uv.x *= 1.0 + (dc.y * (0.3 * warp)); uv.x += 0.5;
+ uv.y -= 0.5; uv.y *= 1.0 + (dc.x * (0.4 * warp)); uv.y += 0.5;
+
+ // sample inside boundaries, otherwise set to black
+ if (uv.y > 1.0 || uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0)
+ fragColor = vec4(0.0, 0.0, 0.0, 1.0);
+ else
+ {
+ // determine if we are drawing in a scanline
+ float apply = abs(sin(fragCoord.y) * 0.5 * scan);
+
+ // sample the texture and apply a teal tint
+ vec3 color = texture(iChannel0, uv).rgb;
+ vec3 tealTint = vec3(0.0, 0.8, 0.6); // teal color (slightly more green than blue)
+
+ // mix the sampled color with the teal tint based on scanline intensity
+ fragColor = vec4(mix(color * tealTint, vec3(0.0), apply), 1.0);
+ }
+}
diff --git a/ghostty/shaders/ripple_cursor.glsl b/ghostty/shaders/ripple_cursor.glsl
new file mode 100644
index 0000000..30a14cf
--- /dev/null
+++ b/ghostty/shaders/ripple_cursor.glsl
@@ -0,0 +1,132 @@
+// CONFIGURATION
+const float DURATION = 0.15; // How long the ripple animates (seconds)
+const float MAX_RADIUS = 0.05; // Max radius in normalized coords (0.5 = 1/4 screen height)
+const float RING_THICKNESS = 0.02; // Ring width in normalized coords
+const float CURSOR_WIDTH_CHANGE_THRESHOLD = 0.5; // Triggers ripple if cursor width changes by this fraction
+vec4 COLOR = iCurrentCursor; // change to iCurrentCursorColor for your cursor's color
+const float BLUR = 3.0; // Blur level in pixels
+const float ANIMATION_START_OFFSET = 0.0; // Start the ripple slightly progressed (0.0 - 1.0)
+
+
+// Easing functions
+float easeOutQuad(float t) {
+ return 1.0 - (1.0 - t) * (1.0 - t);
+}
+float easeInOutQuad(float t) {
+ return t < 0.5 ? 2.0 * t * t : 1.0 - pow(-2.0 * t + 2.0, 2.0) / 2.0;
+}
+float easeOutCubic(float t) {
+ return 1.0 - pow(1.0 - t, 3.0);
+}
+float easeOutQuart(float t) {
+ return 1.0 - pow(1.0 - t, 4.0);
+}
+float easeOutQuint(float t) {
+ return 1.0 - pow(1.0 - t, 5.0);
+}
+float easeOutExpo(float t) {
+ return t == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * t);
+}
+float easeOutCirc(float t) {
+ return sqrt(1.0 - pow(t - 1.0, 2.0));
+}
+float easeOutSine(float t) {
+ return sin((t * 3.1415916) / 2.0);
+}
+float easeOutElastic(float t) {
+ const float c4 = (2.0 * 3.1415916) / 3.0;
+ return t == 0.0 ? 0.0 : t == 1.0 ? 1.0 : pow(2.0, -10.0 * t) * sin((t * 10.0 - 0.75) * c4) + 1.0;
+}
+float easeOutBounce(float t) {
+ const float n1 = 7.5625;
+ const float d1 = 2.75;
+ if (t < 1.0 / d1) {
+ return n1 * t * t;
+ } else if (t < 2.0 / d1) {
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
+ } else if (t < 2.5 / d1) {
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
+ } else {
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
+ }
+}
+float easeOutBack(float t) {
+ const float c1 = 1.70158;
+ const float c3 = c1 + 1.0;
+ return 1.0 + c3 * pow(t - 1.0, 3.0) + c1 * pow(t - 1.0, 2.0);
+}
+
+// Pulse fade functions
+float easeOutPulse(float t) {
+ return t * (2.0 - t);
+}
+float exponentialDecayPulse(float t) {
+ return exp(-3.0 * t) * sin(t * 3.1415916);
+}
+
+vec2 normalize(vec2 value, float isPosition) {
+ return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord){
+ #if !defined(WEB)
+ fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
+ #endif
+
+ // Normalization & setup (-1 to 1 coords)
+ vec2 vu = normalize(fragCoord, 1.);
+ vec2 offsetFactor = vec2(-.5, 0.5);
+
+ vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
+ vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
+
+ vec2 centerCC = currentCursor.xy - (currentCursor.zw * offsetFactor);
+
+ float cellWidth = max(currentCursor.z, previousCursor.z); // width of the 'block' cursor
+
+ // check for significant width change
+ float widthChange = abs(currentCursor.z - previousCursor.z);
+ float widthThresholdNorm = cellWidth * CURSOR_WIDTH_CHANGE_THRESHOLD;
+ float isModeChange = step(widthThresholdNorm, widthChange);
+
+
+ // ANIMATION
+ float rippleProgress = (iTime - iTimeCursorChange) / DURATION + ANIMATION_START_OFFSET;
+ // don't clamp yet; we need to know if it's > 1.0 (finished)
+ float isAnimating = 1.0 - step(1.0, rippleProgress); // progress < 1.0 ? 1.0: 0.0
+
+ if (isModeChange > 0.0 && isAnimating > 0.0) {
+ // Apply easing to progress
+ // float easedProgress = rippleProgress;
+ // float easedProgress = easeOutQuad(rippleProgress);
+ // float easedProgress = easeInOutQuad(rippleProgress);
+ // float easedProgress = easeOutCubic(rippleProgress);
+ // float easedProgress = easeOutQuart(rippleProgress);
+ // float easedProgress = easeOutQuint(rippleProgress);
+ // float easedProgress = easeOutExpo(rippleProgress);
+ float easedProgress = easeOutCirc(rippleProgress);
+ // float easedProgress = easeOutSine(rippleProgress);
+ // float easedProgress = easeOutBack(rippleProgress);
+
+ // RIPPLE CALCULATION
+ float rippleRadius = easedProgress * MAX_RADIUS;
+
+ // float fade = 1.0; // no fade
+ // float fade = 1.0 - easedProgress; // linear fade
+ float fade = 1.0 - easeOutPulse(rippleProgress);
+ // float fade = 1.0 - exponentialDecayPulse(rippleProgress);
+
+ // Calculate distance from frag to cursor center
+ float dist = distance(vu, centerCC);
+
+ float sdfRing = abs(dist - rippleRadius) - RING_THICKNESS * 0.5;
+
+ // Antialias (1-pixel width in normalized coords)
+ float antiAliasSize = normalize(vec2(BLUR, BLUR), 0.0).x;
+ float ripple = (1.0 - smoothstep(-antiAliasSize, antiAliasSize, sdfRing)) * fade;
+
+ // Apply ripple effect
+ fragColor = mix(fragColor, COLOR, ripple * COLOR.a);
+ }
+ // else: do nothing, keep original fragColor
+}
diff --git a/ghostty/shaders/smoke-and-ghost.glsl b/ghostty/shaders/smoke-and-ghost.glsl
new file mode 100644
index 0000000..a11f8dc
--- /dev/null
+++ b/ghostty/shaders/smoke-and-ghost.glsl
@@ -0,0 +1,193 @@
+// Settings for detection
+#define TARGET_COLOR vec3(0.0, 0.0, 0.0) // RGB target pixels to transform
+#define REPLACE_COLOR vec3(0.0, 0.0, 0.0) // Color to replace target pixels
+#define COLOR_TOLERANCE 0.001 // Color matching tolerance
+
+// Smoke effect settings
+#define SMOKE_COLOR vec3(1., 1., 1.0) // Base color of smoke
+#define SMOKE_RADIUS 0.011 // How far the smoke spreads
+#define SMOKE_SPEED 0.5 // Speed of smoke movement
+#define SMOKE_SCALE 25.0 // Scale of smoke detail
+#define SMOKE_INTENSITY 0.2 // Intensity of the smoke effect
+#define SMOKE_RISE_HEIGHT 0.14 // How high the smoke rises
+#define ALPHA_MAX 0.5 // Maximum opacity for smoke
+#define VERTICAL_BIAS 1.0
+
+// Ghost face settings
+#define FACE_COUNT 1 // Number of ghost faces
+#define FACE_SCALE vec2(0.03, 0.05) // Size of faces, can be wider/elongated
+#define FACE_DURATION 1.2 // How long faces last, can be wider/elongated
+#define FACE_TRANSITION 1.5 // Face fade in/out duration
+#define FACE_COLOR vec3(0.0, 0.0, 0.0)
+#define GHOST_BG_COLOR vec3(1.0, 1.0, 1.0)
+#define GHOST_BG_SCALE vec2(0.03, 0.06)
+
+float random(vec2 st) {
+ return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
+}
+
+float random1(float n) {
+ return fract(sin(n) * 43758.5453123);
+}
+
+vec2 random2(float n) {
+ return vec2(
+ random1(n),
+ random1(n + 1234.5678)
+ );
+}
+
+float noise(vec2 st) {
+ vec2 i = floor(st);
+ vec2 f = fract(st);
+
+ float a = random(i);
+ float b = random(i + vec2(1.0, 0.0));
+ float c = random(i + vec2(0.0, 1.0));
+ float d = random(i + vec2(1.0, 1.0));
+
+ vec2 u = f * f * (3.0 - 2.0 * f);
+ return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
+}
+
+// Modified elongated ellipse for more cartoon-like shapes
+float cartoonEllipse(vec2 uv, vec2 center, vec2 scale) {
+ vec2 d = (uv - center) / scale;
+ float len = length(d);
+ // Add cartoon-like falloff
+ return smoothstep(1.0, 0.8, len);
+}
+
+// Function to create ghost background shape
+float ghostBackground(vec2 uv, vec2 center) {
+ vec2 d = (uv - center) / GHOST_BG_SCALE;
+ float baseShape = length(d * vec2(1.0, 0.8)); // Slightly oval
+
+ // Add wavy bottom
+ float wave = sin(d.x * 6.28 + iTime) * 0.2;
+ float bottomWave = smoothstep(0.0, -0.5, d.y + wave);
+
+ return smoothstep(1.0, 0.8, baseShape) + bottomWave;
+}
+
+float ghostFace(vec2 uv, vec2 center, float time, float seed) {
+ vec2 faceUV = (uv - center) / FACE_SCALE;
+
+ float eyeSize = 0.25 + random1(seed) * 0.05;
+ float eyeSpacing = 0.35;
+ vec2 leftEyePos = vec2(-eyeSpacing, 0.2);
+ vec2 rightEyePos = vec2(eyeSpacing, 0.2);
+
+ float leftEye = cartoonEllipse(faceUV, leftEyePos, vec2(eyeSize));
+ float rightEye = cartoonEllipse(faceUV, rightEyePos, vec2(eyeSize));
+
+ // Add simple eye highlights
+ float leftHighlight = cartoonEllipse(faceUV, leftEyePos + vec2(0.1, 0.1), vec2(eyeSize * 0.3));
+ float rightHighlight = cartoonEllipse(faceUV, rightEyePos + vec2(0.1, 0.1), vec2(eyeSize * 0.3));
+
+ vec2 mouthUV = faceUV - vec2(0.0, -0.9);
+ float mouthWidth = 0.5 + random1(seed + 3.0) * 0.1;
+ float mouthHeight = 0.8 + random1(seed + 7.0) * 0.1;
+
+ float mouth = cartoonEllipse(mouthUV, vec2(0.0), vec2(mouthWidth, mouthHeight));
+
+ // Combine features
+ float face = max(max(leftEye, rightEye), mouth);
+ face = max(face, max(leftHighlight, rightHighlight));
+
+ // Add border falloff
+ face *= smoothstep(1.2, 0.8, length(faceUV));
+
+ return face;
+}
+
+float calculateSmoke(vec2 uv, vec2 sourcePos) {
+ float verticalDisp = (uv.y - sourcePos.y) * VERTICAL_BIAS;
+ vec2 smokeUV = uv * SMOKE_SCALE;
+ smokeUV.y -= iTime * SMOKE_SPEED * (1.0 + verticalDisp);
+ smokeUV.x += sin(iTime * 0.5 + uv.y * 4.0) * 0.1;
+
+ float n = noise(smokeUV) * 0.5 + 0.5;
+ n += noise(smokeUV * 2.0 + iTime * 0.1) * 0.25;
+
+ float verticalFalloff = 1.0 - smoothstep(0.0, SMOKE_RISE_HEIGHT, verticalDisp);
+ return n * verticalFalloff;
+}
+
+float isTargetPixel(vec2 uv) {
+ vec4 color = texture(iChannel0, uv);
+ return float(all(lessThan(abs(color.rgb - TARGET_COLOR), vec3(COLOR_TOLERANCE))));
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord/iResolution.xy;
+ vec4 originalColor = texture(iChannel0, uv);
+
+ // Calculate smoke effect
+ float smokeAccum = 0.0;
+ float targetInfluence = 0.0;
+
+ float stepSize = SMOKE_RADIUS / 4.0;
+ for (float x = -SMOKE_RADIUS; x <= SMOKE_RADIUS; x += stepSize) {
+ for (float y = -SMOKE_RADIUS; y <= 0.0; y += stepSize) {
+ vec2 offset = vec2(x, y);
+ vec2 sampleUV = uv + offset;
+
+ if (sampleUV.x >= 0.0 && sampleUV.x <= 1.0 &&
+ sampleUV.y >= 0.0 && sampleUV.y <= 1.0) {
+ float isTarget = isTargetPixel(sampleUV);
+ if (isTarget > 0.0) {
+ float dist = length(offset);
+ float falloff = 1.0 - smoothstep(0.0, SMOKE_RADIUS, dist);
+ float smoke = calculateSmoke(uv, sampleUV);
+ smokeAccum += smoke * falloff;
+ targetInfluence += falloff;
+ }
+ }
+ }
+ }
+
+ smokeAccum /= max(targetInfluence, 1.0);
+ targetInfluence = smoothstep(0.0, 1.0, targetInfluence);
+ float smokePresence = smokeAccum * targetInfluence;
+
+ // Calculate ghost faces with backgrounds
+ float faceAccum = 0.0;
+ float bgAccum = 0.0;
+ float timeBlock = floor(iTime / FACE_DURATION);
+
+ if (smokePresence > 0.2) {
+ for (int i = 0; i < FACE_COUNT; i++) {
+ vec2 facePos = random2(timeBlock + float(i) * 1234.5);
+ facePos = facePos * 0.8 + 0.1;
+
+ float faceTime = mod(iTime, FACE_DURATION);
+ float fadeFactor = smoothstep(0.0, FACE_TRANSITION, faceTime) *
+ (1.0 - smoothstep(FACE_DURATION - FACE_TRANSITION, FACE_DURATION, faceTime));
+
+ // Add ghost background
+ float ghostBg = ghostBackground(uv, facePos) * fadeFactor;
+ bgAccum = max(bgAccum, ghostBg);
+
+ // Add face features
+ float face = ghostFace(uv, facePos, iTime, timeBlock + float(i) * 100.0) * fadeFactor;
+ faceAccum = max(faceAccum, face);
+ }
+
+ bgAccum *= smoothstep(0.2, 0.4, smokePresence);
+ faceAccum *= smoothstep(0.2, 0.4, smokePresence);
+ }
+
+ // Combine all elements
+ bool isTarget = all(lessThan(abs(originalColor.rgb - TARGET_COLOR), vec3(COLOR_TOLERANCE)));
+ vec3 baseColor = isTarget ? REPLACE_COLOR : originalColor.rgb;
+
+ // Layer the effects: base -> smoke -> ghost background -> face features
+ vec3 smokeEffect = mix(baseColor, SMOKE_COLOR, smokeAccum * SMOKE_INTENSITY * targetInfluence * (1.0 - faceAccum));
+ vec3 withBackground = mix(smokeEffect, GHOST_BG_COLOR, bgAccum * 0.7);
+ vec3 finalColor = mix(withBackground, FACE_COLOR, faceAccum);
+
+ float alpha = mix(originalColor.a, ALPHA_MAX, max(smokePresence, max(bgAccum, faceAccum) * smokePresence));
+
+ fragColor = vec4(finalColor, alpha);
+}
diff --git a/ghostty/shaders/sparks-from-fire.glsl b/ghostty/shaders/sparks-from-fire.glsl
new file mode 100644
index 0000000..e48b6e1
--- /dev/null
+++ b/ghostty/shaders/sparks-from-fire.glsl
@@ -0,0 +1,242 @@
+// adapted by Alex Sherwin for Ghstty from https://www.shadertoy.com/view/wl2Gzc
+
+//Shader License: CC BY 3.0
+//Author: Jan Mróz (jaszunio15)
+
+#define SMOKE_INTENSITY_MULTIPLIER 0.9
+#define PARTICLES_ALPHA_MOD 0.9
+#define SMOKE_ALPHA_MOD 0.5
+#define LAYERS_COUNT 8
+
+#define BLACK_BLEND_THRESHOLD .4
+
+#define VEC3_1 (vec3(1.0))
+
+#define PI 3.1415927
+#define TWO_PI 6.283185
+
+#define ANIMATION_SPEED 1.0
+#define MOVEMENT_SPEED .33
+#define MOVEMENT_DIRECTION vec2(0.7, 1.0)
+
+#define PARTICLE_SIZE 0.0025
+
+#define PARTICLE_SCALE (vec2(0.5, 1.6))
+#define PARTICLE_SCALE_VAR (vec2(0.25, 0.2))
+
+#define PARTICLE_BLOOM_SCALE (vec2(0.5, 0.8))
+#define PARTICLE_BLOOM_SCALE_VAR (vec2(0.3, 0.1))
+
+#define SPARK_COLOR vec3(1.0, 0.4, 0.05) * 1.5
+#define BLOOM_COLOR vec3(1.0, 0.4, 0.05) * 0.8
+#define SMOKE_COLOR vec3(1.0, 0.43, 0.1) * 0.8
+
+#define SIZE_MOD 1.05
+
+
+float hash1_2(in vec2 x)
+{
+ return fract(sin(dot(x, vec2(52.127, 61.2871))) * 521.582);
+}
+
+vec2 hash2_2(in vec2 x)
+{
+ return fract(sin(x * mat2x2(20.52, 24.1994, 70.291, 80.171)) * 492.194);
+}
+
+//Simple interpolated noise
+vec2 noise2_2(vec2 uv)
+{
+ //vec2 f = fract(uv);
+ vec2 f = smoothstep(0.0, 1.0, fract(uv));
+
+ vec2 uv00 = floor(uv);
+ vec2 uv01 = uv00 + vec2(0,1);
+ vec2 uv10 = uv00 + vec2(1,0);
+ vec2 uv11 = uv00 + 1.0;
+ vec2 v00 = hash2_2(uv00);
+ vec2 v01 = hash2_2(uv01);
+ vec2 v10 = hash2_2(uv10);
+ vec2 v11 = hash2_2(uv11);
+
+ vec2 v0 = mix(v00, v01, f.y);
+ vec2 v1 = mix(v10, v11, f.y);
+ vec2 v = mix(v0, v1, f.x);
+
+ return v;
+}
+
+//Simple interpolated noise
+float noise1_2(in vec2 uv)
+{
+ vec2 f = fract(uv);
+ //vec2 f = smoothstep(0.0, 1.0, fract(uv));
+
+ vec2 uv00 = floor(uv);
+ vec2 uv01 = uv00 + vec2(0,1);
+ vec2 uv10 = uv00 + vec2(1,0);
+ vec2 uv11 = uv00 + 1.0;
+
+ float v00 = hash1_2(uv00);
+ float v01 = hash1_2(uv01);
+ float v10 = hash1_2(uv10);
+ float v11 = hash1_2(uv11);
+
+ float v0 = mix(v00, v01, f.y);
+ float v1 = mix(v10, v11, f.y);
+ float v = mix(v0, v1, f.x);
+
+ return v;
+}
+
+
+float layeredNoise1_2(in vec2 uv, in float sizeMod, in float alphaMod, in int layers, in float animation)
+{
+ float noise = 0.0;
+ float alpha = 1.0;
+ float size = 1.0;
+ vec2 offset;
+ for (int i = 0; i < layers; i++)
+ {
+ offset += hash2_2(vec2(alpha, size)) * 10.0;
+
+ //Adding noise with movement
+ noise += noise1_2(uv * size + iTime * animation * 8.0 * MOVEMENT_DIRECTION * MOVEMENT_SPEED + offset) * alpha;
+ alpha *= alphaMod;
+ size *= sizeMod;
+ }
+
+ noise *= (1.0 - alphaMod)/(1.0 - pow(alphaMod, float(layers)));
+ return noise;
+}
+
+//Rotates point around 0,0
+vec2 rotate(in vec2 point, in float deg)
+{
+ float s = sin(deg);
+ float c = cos(deg);
+ return mat2x2(s, c, -c, s) * point;
+}
+
+//Cell center from point on the grid
+vec2 voronoiPointFromRoot(in vec2 root, in float deg)
+{
+ vec2 point = hash2_2(root) - 0.5;
+ float s = sin(deg);
+ float c = cos(deg);
+ point = mat2x2(s, c, -c, s) * point * 0.66;
+ point += root + 0.5;
+ return point;
+}
+
+//Voronoi cell point rotation degrees
+float degFromRootUV(in vec2 uv)
+{
+ return iTime * ANIMATION_SPEED * (hash1_2(uv) - 0.5) * 2.0;
+}
+
+vec2 randomAround2_2(in vec2 point, in vec2 range, in vec2 uv)
+{
+ return point + (hash2_2(uv) - 0.5) * range;
+}
+
+
+vec3 fireParticles(in vec2 uv, in vec2 originalUV)
+{
+ vec3 particles = vec3(0.0);
+ vec2 rootUV = floor(uv);
+ float deg = degFromRootUV(rootUV);
+ vec2 pointUV = voronoiPointFromRoot(rootUV, deg);
+ float dist = 2.0;
+ float distBloom = 0.0;
+
+ //UV manipulation for the faster particle movement
+ vec2 tempUV = uv + (noise2_2(uv * 2.0) - 0.5) * 0.1;
+ tempUV += -(noise2_2(uv * 3.0 + iTime) - 0.5) * 0.07;
+
+ //Sparks sdf
+ dist = length(rotate(tempUV - pointUV, 0.7) * randomAround2_2(PARTICLE_SCALE, PARTICLE_SCALE_VAR, rootUV));
+
+ //Bloom sdf
+ distBloom = length(rotate(tempUV - pointUV, 0.7) * randomAround2_2(PARTICLE_BLOOM_SCALE, PARTICLE_BLOOM_SCALE_VAR, rootUV));
+
+ //Add sparks
+ particles += (1.0 - smoothstep(PARTICLE_SIZE * 0.6, PARTICLE_SIZE * 3.0, dist)) * SPARK_COLOR;
+
+ //Add bloom
+ particles += pow((1.0 - smoothstep(0.0, PARTICLE_SIZE * 6.0, distBloom)) * 1.0, 3.0) * BLOOM_COLOR;
+
+ //Upper disappear curve randomization
+ float border = (hash1_2(rootUV) - 0.5) * 2.0;
+ float disappear = 1.0 - smoothstep(border, border + 0.5, originalUV.y);
+
+ //Lower appear curve randomization
+ border = (hash1_2(rootUV + 0.214) - 1.8) * 0.7;
+ float appear = smoothstep(border, border + 0.4, originalUV.y);
+
+ return particles * disappear * appear;
+}
+
+
+//Layering particles to imitate 3D view
+vec3 layeredParticles(in vec2 uv, in float sizeMod, in float alphaMod, in int layers, in float smoke)
+{
+ vec3 particles = vec3(0);
+ float size = 1.0;
+ // float alpha = 1.0;
+ float alpha = 1.0;
+ vec2 offset = vec2(0.0);
+ vec2 noiseOffset;
+ vec2 bokehUV;
+
+ for (int i = 0; i < layers; i++)
+ {
+ //Particle noise movement
+ noiseOffset = (noise2_2(uv * size * 2.0 + 0.5) - 0.5) * 0.15;
+
+ //UV with applied movement
+ bokehUV = (uv * size + iTime * MOVEMENT_DIRECTION * MOVEMENT_SPEED) + offset + noiseOffset;
+
+ //Adding particles if there is more smoke, remove smaller particles
+ particles += fireParticles(bokehUV, uv) * alpha * (1.0 - smoothstep(0.0, 1.0, smoke) * (float(i) / float(layers)));
+
+ //Moving uv origin to avoid generating the same particles
+ offset += hash2_2(vec2(alpha, alpha)) * 10.0;
+
+ alpha *= alphaMod;
+ size *= sizeMod;
+ }
+
+ return particles;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.x;
+
+ // float vignette = 1.1 - smoothstep(0.4, 1.4, length(uv + vec2(0.0, 0.3)));
+ float vignette = 1.3 - smoothstep(0.4, 1.4, length(uv + vec2(0.0, 0.3)));
+
+ uv *= 2.5;
+
+ float smokeIntensity = layeredNoise1_2(uv * 10.0 + iTime * 4.0 * MOVEMENT_DIRECTION * MOVEMENT_SPEED, 1.7, 0.7, 6, 0.2);
+ smokeIntensity *= pow(smoothstep(-1.0, 1.6, uv.y), 2.0);
+ vec3 smoke = smokeIntensity * SMOKE_COLOR * vignette * SMOKE_INTENSITY_MULTIPLIER * SMOKE_ALPHA_MOD;
+
+ //Cutting holes in smoke
+ smoke *= pow(layeredNoise1_2(uv * 4.0 + iTime * 0.5 * MOVEMENT_DIRECTION * MOVEMENT_SPEED, 1.8, 0.5, 3, 0.2), 2.0) * 1.5;
+
+ vec3 particles = layeredParticles(uv, SIZE_MOD, PARTICLES_ALPHA_MOD, LAYERS_COUNT, smokeIntensity);
+
+ vec3 col = particles + smoke + SMOKE_COLOR * 0.02;
+ col *= vignette;
+
+ col = smoothstep(-0.08, 1.0, col);
+
+ vec2 termUV = fragCoord.xy / iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, termUV);
+
+ float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
+ vec3 blendedColor = mix(terminalColor.rgb, col, alpha);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/spotlight.glsl b/ghostty/shaders/spotlight.glsl
new file mode 100644
index 0000000..19f457a
--- /dev/null
+++ b/ghostty/shaders/spotlight.glsl
@@ -0,0 +1,42 @@
+// Created by Paul Robello
+
+
+// Smooth oscillating function that varies over time
+float smoothOscillation(float t, float frequency, float phase) {
+ return sin(t * frequency + phase);
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ // Resolution and UV coordinates
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ // Used to fix distortion when calculating distance to circle center
+ vec2 ratio = vec2(iResolution.x / iResolution.y, 1.0);
+
+ // Get the texture from iChannel0
+ vec4 texColor = texture(iChannel0, uv);
+
+ // Spotlight center moving based on a smooth random pattern
+ float time = iTime * 1.0; // Control speed of motion
+ vec2 spotlightCenter = vec2(
+ 0.5 + 0.4 * smoothOscillation(time, 1.0, 0.0), // Smooth X motion
+ 0.5 + 0.4 * smoothOscillation(time, 1.3, 3.14159) // Smooth Y motion with different frequency and phase
+ );
+
+ // Distance from the spotlight center
+ float distanceToCenter = distance(uv * ratio, spotlightCenter);
+
+ // Spotlight intensity based on distance
+ float spotlightRadius = 0.25; // Spotlight radius
+ float softness = 20.0; // Spotlight edge softness. Higher values have sharper edge
+ float spotlightIntensity = smoothstep(spotlightRadius, spotlightRadius - (1.0 / softness), distanceToCenter);
+
+ // Ambient light level
+ float ambientLight = 0.5; // Controls the minimum brightness across the texture
+
+ // Combine the spotlight effect with the texture
+ vec3 spotlightEffect = texColor.rgb * mix(vec3(ambientLight), vec3(1.0), spotlightIntensity);
+
+ // Final color output
+ fragColor = vec4(spotlightEffect, texColor.a);
+}
\ No newline at end of file
diff --git a/ghostty/shaders/starfield-colors.glsl b/ghostty/shaders/starfield-colors.glsl
new file mode 100644
index 0000000..cf125a9
--- /dev/null
+++ b/ghostty/shaders/starfield-colors.glsl
@@ -0,0 +1,145 @@
+// divisions of grid
+const float repeats = 30.;
+
+// number of layers
+const float layers = 21.;
+
+// star colours
+const vec3 blue = vec3(51.,64.,195.)/255.;
+const vec3 cyan = vec3(117.,250.,254.)/255.;
+const vec3 white = vec3(255.,255.,255.)/255.;
+const vec3 yellow = vec3(251.,245.,44.)/255.;
+const vec3 red = vec3(247,2.,20.)/255.;
+
+// spectrum function
+vec3 spectrum(vec2 pos){
+ pos.x *= 4.;
+ vec3 outCol = vec3(0);
+ if( pos.x > 0.){
+ outCol = mix(blue, cyan, fract(pos.x));
+ }
+ if( pos.x > 1.){
+ outCol = mix(cyan, white, fract(pos.x));
+ }
+ if( pos.x > 2.){
+ outCol = mix(white, yellow, fract(pos.x));
+ }
+ if( pos.x > 3.){
+ outCol = mix(yellow, red, fract(pos.x));
+ }
+
+ return 1.-(pos.y * (1.-outCol));
+}
+
+float N21(vec2 p) {
+ p = fract(p * vec2(233.34, 851.73));
+ p += dot(p, p + 23.45);
+ return fract(p.x * p.y);
+}
+
+vec2 N22(vec2 p) {
+ float n = N21(p);
+ return vec2(n, N21(p + n));
+}
+
+mat2 scale(vec2 _scale) {
+ return mat2(_scale.x, 0.0,
+ 0.0, _scale.y);
+}
+
+// 2D Noise based on Morgan McGuire
+float noise(in vec2 st) {
+ vec2 i = floor(st);
+ vec2 f = fract(st);
+
+ // Four corners in 2D of a tile
+ float a = N21(i);
+ float b = N21(i + vec2(1.0, 0.0));
+ float c = N21(i + vec2(0.0, 1.0));
+ float d = N21(i + vec2(1.0, 1.0));
+
+ // Smooth Interpolation
+ vec2 u = f * f * (3.0 - 2.0 * f); // Cubic Hermite Curve
+
+ // Mix 4 corners percentages
+ return mix(a, b, u.x) +
+ (c - a) * u.y * (1.0 - u.x) +
+ (d - b) * u.x * u.y;
+}
+
+float perlin2(vec2 uv, int octaves, float pscale) {
+ float col = 1.;
+ float initScale = 4.;
+ for (int l; l < octaves; l++) {
+ float val = noise(uv * initScale);
+ if (col <= 0.01) {
+ col = 0.;
+ break;
+ }
+ val -= 0.01;
+ val *= 0.5;
+ col *= val;
+ initScale *= pscale;
+ }
+ return col;
+}
+
+vec3 stars(vec2 uv, float offset) {
+ float timeScale = -(iTime + offset) / layers;
+ float trans = fract(timeScale);
+ float newRnd = floor(timeScale);
+ vec3 col = vec3(0.);
+
+ // Translate uv then scale for center
+ uv -= vec2(0.5);
+ uv = scale(vec2(trans)) * uv;
+ uv += vec2(0.5);
+
+ // Create square aspect ratio
+ uv.x *= iResolution.x / iResolution.y;
+
+ // Create boxes
+ uv *= repeats;
+
+ // Get position
+ vec2 ipos = floor(uv);
+
+ // Return uv as 0 to 1
+ uv = fract(uv);
+
+ // Calculate random xy and size
+ vec2 rndXY = N22(newRnd + ipos * (offset + 1.)) * 0.9 + 0.05;
+ float rndSize = N21(ipos) * 100. + 200.;
+
+ vec2 j = (rndXY - uv) * rndSize;
+ float sparkle = 1. / dot(j, j);
+
+ // Set stars to be pure white
+ col += spectrum(fract(rndXY*newRnd*ipos)) * vec3(sparkle);
+
+ col *= smoothstep(1., 0.8, trans);
+ return col; // Return pure white stars only
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ // Normalized pixel coordinates (from 0 to 1)
+ vec2 uv = fragCoord/iResolution.xy;
+
+ vec3 col = vec3(0.);
+
+ for (float i = 0.; i < layers; i++ ){
+ col += stars(uv, i);
+ }
+
+ // Sample the terminal screen texture including alpha channel
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Make a mask that is 1.0 where the terminal content is not black
+ float mask = 1 - step(0.5, dot(terminalColor.rgb, vec3(1.0)));
+ vec3 blendedColor = mix(terminalColor.rgb, col, mask);
+
+ // Apply terminal's alpha to control overall opacity
+ fragColor = vec4(blendedColor, terminalColor.a);
+
+}
diff --git a/ghostty/shaders/starfield.glsl b/ghostty/shaders/starfield.glsl
new file mode 100644
index 0000000..d5fa23c
--- /dev/null
+++ b/ghostty/shaders/starfield.glsl
@@ -0,0 +1,125 @@
+// divisions of grid
+const float repeats = 30.;
+
+// number of layers
+const float layers = 21.;
+
+// star colors
+const vec3 white = vec3(1.0); // Set star color to pure white
+
+float N21(vec2 p) {
+ p = fract(p * vec2(233.34, 851.73));
+ p += dot(p, p + 23.45);
+ return fract(p.x * p.y);
+}
+
+vec2 N22(vec2 p) {
+ float n = N21(p);
+ return vec2(n, N21(p + n));
+}
+
+mat2 scale(vec2 _scale) {
+ return mat2(_scale.x, 0.0,
+ 0.0, _scale.y);
+}
+
+// 2D Noise based on Morgan McGuire
+float noise(in vec2 st) {
+ vec2 i = floor(st);
+ vec2 f = fract(st);
+
+ // Four corners in 2D of a tile
+ float a = N21(i);
+ float b = N21(i + vec2(1.0, 0.0));
+ float c = N21(i + vec2(0.0, 1.0));
+ float d = N21(i + vec2(1.0, 1.0));
+
+ // Smooth Interpolation
+ vec2 u = f * f * (3.0 - 2.0 * f); // Cubic Hermite Curve
+
+ // Mix 4 corners percentages
+ return mix(a, b, u.x) +
+ (c - a) * u.y * (1.0 - u.x) +
+ (d - b) * u.x * u.y;
+}
+
+float perlin2(vec2 uv, int octaves, float pscale) {
+ float col = 1.;
+ float initScale = 4.;
+ for (int l; l < octaves; l++) {
+ float val = noise(uv * initScale);
+ if (col <= 0.01) {
+ col = 0.;
+ break;
+ }
+ val -= 0.01;
+ val *= 0.5;
+ col *= val;
+ initScale *= pscale;
+ }
+ return col;
+}
+
+vec3 stars(vec2 uv, float offset) {
+ float timeScale = -(iTime + offset) / layers;
+ float trans = fract(timeScale);
+ float newRnd = floor(timeScale);
+ vec3 col = vec3(0.);
+
+ // Translate uv then scale for center
+ uv -= vec2(0.5);
+ uv = scale(vec2(trans)) * uv;
+ uv += vec2(0.5);
+
+ // Create square aspect ratio
+ uv.x *= iResolution.x / iResolution.y;
+
+ // Create boxes
+ uv *= repeats;
+
+ // Get position
+ vec2 ipos = floor(uv);
+
+ // Return uv as 0 to 1
+ uv = fract(uv);
+
+ // Calculate random xy and size
+ vec2 rndXY = N22(newRnd + ipos * (offset + 1.)) * 0.9 + 0.05;
+ float rndSize = N21(ipos) * 100. + 200.;
+
+ vec2 j = (rndXY - uv) * rndSize;
+ float sparkle = 1. / dot(j, j);
+
+ // Set stars to be pure white
+ col += white * sparkle;
+
+ col *= smoothstep(1., 0.8, trans);
+ return col; // Return pure white stars only
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ // Normalized pixel coordinates (from 0 to 1)
+ vec2 uv = fragCoord/iResolution.xy;
+
+ vec3 col = vec3(0.);
+
+ for (float i = 0.; i < layers; i++ ){
+ col += stars(uv, i);
+ }
+
+
+ // Output to screen
+ // fragColor = vec4(col,1.0);
+
+ // Sample the terminal screen texture including alpha channel
+ vec4 terminalColor = texture(iChannel0, uv);
+
+ // Make a mask that is 1.0 where the terminal content is not black
+ float mask = 1 - step(0.5, dot(terminalColor.rgb, vec3(1.0)));
+ vec3 blendedColor = mix(terminalColor.rgb, col, mask);
+
+ // Apply terminal's alpha to control overall opacity
+ fragColor = vec4(blendedColor, terminalColor.a);
+
+}
diff --git a/ghostty/shaders/tft.glsl b/ghostty/shaders/tft.glsl
new file mode 100644
index 0000000..3d77443
--- /dev/null
+++ b/ghostty/shaders/tft.glsl
@@ -0,0 +1,23 @@
+/** Size of TFT "pixels" */
+float resolution = 4.0;
+
+/** Strength of effect */
+float strength = 0.5;
+
+void _scanline(inout vec3 color, vec2 uv)
+{
+ float scanline = step(1.2, mod(uv.y * iResolution.y, resolution));
+ float grille = step(1.2, mod(uv.x * iResolution.x, resolution));
+ color *= max(1.0 - strength, scanline * grille);
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord)
+{
+ vec2 uv = fragCoord.xy / iResolution.xy;
+ vec3 color = texture(iChannel0, uv).rgb;
+
+ _scanline(color, uv);
+
+ fragColor.xyz = color;
+ fragColor.w = 1.0;
+}
diff --git a/ghostty/shaders/underwater.glsl b/ghostty/shaders/underwater.glsl
new file mode 100644
index 0000000..8c2fb22
--- /dev/null
+++ b/ghostty/shaders/underwater.glsl
@@ -0,0 +1,74 @@
+// adapted by Alex Sherwin for Ghostty from https://www.shadertoy.com/view/lljGDt
+
+#define BLACK_BLEND_THRESHOLD .4
+
+float hash21(vec2 p) {
+ p = fract(p * vec2(233.34, 851.73));
+ p += dot(p, p + 23.45);
+ return fract(p.x * p.y);
+}
+
+float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed)
+{
+ vec2 sourceToCoord = coord - raySource;
+ float cosAngle = dot(normalize(sourceToCoord), rayRefDirection);
+
+ // Add subtle dithering based on screen coordinates
+ float dither = hash21(coord) * 0.015 - 0.0075;
+
+ float ray = clamp(
+ (0.45 + 0.15 * sin(cosAngle * seedA + iTime * speed)) +
+ (0.3 + 0.2 * cos(-cosAngle * seedB + iTime * speed)) + dither,
+ 0.0, 1.0);
+
+ // Smoothstep the distance falloff
+ float distFade = smoothstep(0.0, iResolution.x, iResolution.x - length(sourceToCoord));
+ return ray * mix(0.5, 1.0, distFade);
+}
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ uv.y = 1.0 - uv.y;
+ vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
+
+ // Set the parameters of the sun rays
+ vec2 rayPos1 = vec2(iResolution.x * 0.7, iResolution.y * 1.1);
+ vec2 rayRefDir1 = normalize(vec2(1.0, 0.116));
+ float raySeedA1 = 36.2214;
+ float raySeedB1 = 21.11349;
+ float raySpeed1 = 1.1;
+
+ vec2 rayPos2 = vec2(iResolution.x * 0.8, iResolution.y * 1.2);
+ vec2 rayRefDir2 = normalize(vec2(1.0, -0.241));
+ const float raySeedA2 = 22.39910;
+ const float raySeedB2 = 18.0234;
+ const float raySpeed2 = 0.9;
+
+ // Calculate the colour of the sun rays on the current fragment
+ vec4 rays1 =
+ vec4(1.0, 1.0, 1.0, 0.0) *
+ rayStrength(rayPos1, rayRefDir1, coord, raySeedA1, raySeedB1, raySpeed1);
+
+ vec4 rays2 =
+ vec4(1.0, 1.0, 1.0, 0.0) *
+ rayStrength(rayPos2, rayRefDir2, coord, raySeedA2, raySeedB2, raySpeed2);
+
+ vec4 col = rays1 * 0.5 + rays2 * 0.4;
+
+ // Attenuate brightness towards the bottom, simulating light-loss due to depth.
+ // Give the whole thing a blue-green tinge as well.
+ float brightness = 1.0 - (coord.y / iResolution.y);
+ col.r *= 0.05 + (brightness * 0.8);
+ col.g *= 0.15 + (brightness * 0.6);
+ col.b *= 0.3 + (brightness * 0.5);
+
+ vec2 termUV = fragCoord.xy / iResolution.xy;
+ vec4 terminalColor = texture(iChannel0, termUV);
+
+ float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
+ vec3 blendedColor = mix(terminalColor.rgb * 1.0, col.rgb * 0.3, alpha);
+
+ fragColor = vec4(blendedColor, terminalColor.a);
+}
diff --git a/ghostty/shaders/water.glsl b/ghostty/shaders/water.glsl
new file mode 100644
index 0000000..c240b58
--- /dev/null
+++ b/ghostty/shaders/water.glsl
@@ -0,0 +1,35 @@
+
+#define TAU 6.28318530718
+#define MAX_ITER 6
+
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+{
+ vec3 water_color = vec3(1.0, 1.0, 1.0) * 0.5;
+ float time = iTime * 0.5+23.0;
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+ vec2 p = mod(uv*TAU, TAU)-250.0;
+ vec2 i = vec2(p);
+ float c = 1.0;
+ float inten = 0.005;
+
+ for (int n = 0; n < MAX_ITER; n++)
+ {
+ float t = time * (1.0 - (3.5 / float(n+1)));
+ i = p + vec2(cos(t - i.x) + sin(t + i.y), sin(t - i.y) + cos(t + i.x));
+ c += 1.0/length(vec2(p.x / (sin(i.x+t)/inten),p.y / (cos(i.y+t)/inten)));
+ }
+ c /= float(MAX_ITER);
+ c = 1.17-pow(c, 1.4);
+ vec3 color = vec3(pow(abs(c), 15.0));
+ color = clamp((color + water_color)*1.2, 0.0, 1.0);
+
+ // perterb uv based on value of c from caustic calc above
+ vec2 tc = vec2(cos(c)-0.75,sin(c)-0.75)*0.04;
+ uv = clamp(uv + tc,0.0,1.0);
+
+ fragColor = texture(iChannel0, uv);
+ // give transparent pixels a color
+ if ( fragColor.a == 0.0 ) fragColor=vec4(1.0,1.0,1.0,1.0);
+ fragColor *= vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/sketchybar/.DS_Store b/sketchybar/.DS_Store
new file mode 100644
index 0000000..16158bd
Binary files /dev/null and b/sketchybar/.DS_Store differ
diff --git a/sketchybar/plugins/aerospace.sh b/sketchybar/plugins/aerospace.sh
new file mode 100755
index 0000000..fad182f
--- /dev/null
+++ b/sketchybar/plugins/aerospace.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+if [ "$1" = "$FOCUSED_WORKSPACE" ]; then
+ sketchybar --set $NAME label.color=0xffcba6f7
+else
+ sketchybar --set $NAME label.color=0x77cdd6f4
+fi
diff --git a/sketchybar/plugins/battery.sh b/sketchybar/plugins/battery.sh
new file mode 100755
index 0000000..9dd9627
--- /dev/null
+++ b/sketchybar/plugins/battery.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+PERCENTAGE="$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)"
+CHARGING="$(pmset -g batt | grep 'AC Power')"
+
+if [ "$PERCENTAGE" = "" ]; then
+ exit 0
+fi
+
+case "${PERCENTAGE}" in
+ 9[0-9]|100) ICON=""
+ ;;
+ [6-8][0-9]) ICON=""
+ ;;
+ [3-5][0-9]) ICON=""
+ ;;
+ [1-2][0-9]) ICON=""
+ ;;
+ *) ICON=""
+esac
+
+if [[ "$CHARGING" != "" ]]; then
+ ICON=""
+fi
+
+# The item invoking this script (name $NAME) will get its icon and label
+# updated with the current battery status
+sketchybar --set "$NAME" icon="$ICON" label="${PERCENTAGE}%"
diff --git a/sketchybar/plugins/clock.sh b/sketchybar/plugins/clock.sh
new file mode 100755
index 0000000..ff2b445
--- /dev/null
+++ b/sketchybar/plugins/clock.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# The $NAME variable is passed from sketchybar and holds the name of
+# the item invoking this script:
+# https://felixkratz.github.io/SketchyBar/config/events#events-and-scripting
+
+sketchybar --set "$NAME" label="$(date '+%A, %d %b, %I:%M %p')"
+
diff --git a/sketchybar/plugins/front_app.sh b/sketchybar/plugins/front_app.sh
new file mode 100755
index 0000000..fb6d0b3
--- /dev/null
+++ b/sketchybar/plugins/front_app.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Some events send additional information specific to the event in the $INFO
+# variable. E.g. the front_app_switched event sends the name of the newly
+# focused application in the $INFO variable:
+# https://felixkratz.github.io/SketchyBar/config/events#events-and-scripting
+
+if [ "$SENDER" = "front_app_switched" ]; then
+ sketchybar --set "$NAME" label="$INFO"
+fi
diff --git a/sketchybar/plugins/space.sh b/sketchybar/plugins/space.sh
new file mode 100755
index 0000000..b8602b5
--- /dev/null
+++ b/sketchybar/plugins/space.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# The $SELECTED variable is available for space components and indicates if
+# the space invoking this script (with name: $NAME) is currently selected:
+# https://felixkratz.github.io/SketchyBar/config/components#space----associate-mission-control-spaces-with-an-item
+
+sketchybar --set "$NAME" background.drawing="$SELECTED"
diff --git a/sketchybar/plugins/volume.sh b/sketchybar/plugins/volume.sh
new file mode 100755
index 0000000..6e69a5d
--- /dev/null
+++ b/sketchybar/plugins/volume.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# The volume_change event supplies a $INFO variable in which the current volume
+# percentage is passed to the script.
+
+if [ "$SENDER" = "volume_change" ]; then
+ VOLUME="$INFO"
+
+ case "$VOLUME" in
+ [6-9][0-9]|100) ICON=""
+ ;;
+ [3-5][0-9]) ICON=""
+ ;;
+ [1-9]|[1-2][0-9]) ICON=""
+ ;;
+ *) ICON=""
+ esac
+
+ sketchybar --set "$NAME" icon="$ICON" label="$VOLUME%"
+fi
diff --git a/sketchybar/plugins/wifi.sh b/sketchybar/plugins/wifi.sh
new file mode 100755
index 0000000..788cb9b
--- /dev/null
+++ b/sketchybar/plugins/wifi.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Get the current Wi-Fi SSID
+wifi_name=$(networksetup -listpreferredwirelessnetworks en0 | sed -n '2 p' | tr -d '\t')
+
+# If the Wi-Fi name is empty, set a default value (e.g., "No Wi-Fi")
+if [ -z "$wifi_name" ]; then
+ wifi_name="Offline"
+fi
+
+# Set the Wi-Fi name as the label in SketchyBar
+sketchybar --set "$NAME" label="$wifi_name"
diff --git a/sketchybar/sketchybarrc b/sketchybar/sketchybarrc
new file mode 100755
index 0000000..c90183a
--- /dev/null
+++ b/sketchybar/sketchybarrc
@@ -0,0 +1,128 @@
+#!/usr/bin/env bash
+
+# === CONFIG ROOT ===
+CONFIG_DIR="$HOME/.config/sketchybar"
+PLUGIN_DIR="$CONFIG_DIR/plugins"
+
+# === COLORS ===
+CLR_BG=0x00000000 # Transparent bar background
+CLR_COMP_BG=0xff1e1e2e # Compartment background (Mocha base)
+CLR_COMP_BORDER=0xff313244 # Compartment border (Mocha surface0)
+CLR_FG=0xffcdd6f4
+CLR_MAUVE=0xffcba6f7
+CLR_BLUE=0xff89b4fa
+CLR_GREEN=0xffa6e3a1
+CLR_RED=0xfff38ba8
+CLR_YELLOW=0xfff9e2af
+CLR_TEAL=0xff94e2d5
+CLR_SUBTEXT=0xff6c7086 # Mocha surface2, good for secondary text
+
+# === PADDING CONSTANTS
+BAR_PADDING=8 # Outer bar padding (use compartments for outer spacing)
+ITEM_PADDING=6 # Default horizontal padding between items
+ICON_PADDING=2 # Consistent icon inner padding (reduced from mixed 2/4 for tighter look)
+LABEL_PADDING=2 # Consistent label inner padding (unified from 4/2)
+COMP_PADDING=6 # Compartment outer padding (increased from bar-level 0 for breathing room)
+
+# === SYSTEM METRICS PROVIDER ===
+#pkill -f stats_provider >/dev/null 2>&1
+#/Users/nate/Documents/scripts/stats_provider --cpu temperature &
+
+# === BAR (No outer padding; defer to compartments) ===
+sketchybar --bar position=top height=34 blur_radius=0 color="$CLR_BG" padding_left="$BAR_PADDING" padding_right="$BAR_PADDING"
+
+# === WORKSPACES ===
+sketchybar --add event aerospace_workspace_change
+LABELS=("" "" "" "" "" "" "" "" "")
+LEFT_SPACE_ITEMS=()
+INDEX=0
+
+for sid in $(aerospace list-workspaces --all); do
+ label="${LABELS[$INDEX]:-}"
+ item="space.$sid"
+ LEFT_SPACE_ITEMS+=("$item")
+
+ sketchybar --add item "$item" left \
+ --subscribe "$item" aerospace_workspace_change \
+ --set "$item" \
+ background.color=0xff000000 \
+ background.corner_radius=5 \
+ background.height=20 \
+ background.drawing=off \
+ label="$label" \
+ icon.padding_left="$ICON_PADDING" \
+ icon.padding_right="$ICON_PADDING" \
+ label.padding_left="$LABEL_PADDING" \
+ label.padding_right="$LABEL_PADDING" \
+ click_script="aerospace workspace $sid" \
+ script="$PLUGIN_DIR/aerospace.sh $sid"
+
+ ((INDEX++))
+done
+
+# === DEFAULTS (Unified paddings for all items/icons/labels) ===
+sketchybar --default \
+ padding_left="$ITEM_PADDING" \
+ padding_right="$ITEM_PADDING" \
+ icon.font="VictorMono Nerd Font:Regular:12.0" \
+ label.font="VictorMono Nerd Font:Regular:15.0" \
+ icon.color="$CLR_FG" \
+ label.color="$CLR_FG" \
+ icon.padding_left="$ICON_PADDING" \
+ icon.padding_right="$ICON_PADDING" \
+ label.padding_left="$LABEL_PADDING" \
+ label.padding_right="$LABEL_PADDING"
+
+# === FRONT APP
+sketchybar --add item front_app left \
+ --set front_app \
+ script="$PLUGIN_DIR/front_app.sh" \
+ label.y_offset=1 \
+ label.padding_right="$COMP_PADDING" \
+ --subscribe front_app front_app_switched
+
+# === RIGHT SIDE ITEMS ===
+sketchybar --add item clock right \
+ --set clock update_freq=10 icon= script="$PLUGIN_DIR/clock.sh" label.padding_right="$COMP_PADDING"
+
+sketchybar --add item volume right \
+ --set volume script="$PLUGIN_DIR/volume.sh" \
+ --subscribe volume volume_change
+
+sketchybar --add item battery right \
+ --set battery update_freq=120 script="$PLUGIN_DIR/battery.sh" \
+ --subscribe battery system_woke power_source_change
+
+sketchybar --add item net right \
+ --set net update_freq=120 icon= script="$PLUGIN_DIR/wifi.sh"
+
+# sketchybar --add item cpu_temp right \
+ # --set cpu_temp icon= script="sketchybar --set cpu_temp label=\$CPU_TEMP" icon.padding_left="$COMP_PADDING" \
+ # --subscribe cpu_temp system_stats
+
+# === ITEM GROUPS ===
+LEFT_ITEMS=("${LEFT_SPACE_ITEMS[@]}" front_app)
+RIGHT_ITEMS=(clock volume battery net cpu_temp)
+
+# === COMPARTMENTS (Outer padding for clean edges; height=26 leaves vertical margin in 34pt bar) ===
+sketchybar --add bracket left_compartment "${LEFT_ITEMS[@]}" \
+ --set left_compartment \
+ background.color="$CLR_COMP_BG" \
+ background.corner_radius=8 \
+ background.height=26 \
+ background.border_color="$CLR_COMP_BORDER" \
+ background.border_width=1 \
+ background.drawing=on
+
+sketchybar --add bracket right_compartment "${RIGHT_ITEMS[@]}" \
+ --set right_compartment \
+ background.color="$CLR_COMP_BG" \
+ background.corner_radius=8 \
+ background.height=26 \
+ background.border_color="$CLR_COMP_BORDER" \
+ background.border_width=1 \
+ background.padding_left=0 \
+ background.drawing=on
+
+# === FINALIZE ===
+sketchybar --update
diff --git a/starship.toml b/starship.toml
new file mode 100644
index 0000000..6c0c03a
--- /dev/null
+++ b/starship.toml
@@ -0,0 +1,68 @@
+add_newline = true
+command_timeout = 2000
+
+format = """$env_var$os$username$hostname$kubernetes$directory$git_branch$git_status$python
+$character
+"""
+
+[character]
+success_symbol = "[╰⎯](#a78bfa)"
+error_symbol = "[╰⎯](#a78bfa)"
+
+[env_var]
+symbol = "[╭╴](#a78bfa)"
+variable = 'SHELL'
+format = "$symbol"
+disabled = false
+
+[os]
+format = '[$symbol](#a78bfa bold) '
+disabled = false
+
+[os.symbols]
+Windows = ' '
+Arch = ''
+Ubuntu = ''
+Macos = ''
+Unknown = ''
+
+[username]
+style_user = 'bold'
+style_root = 'bold'
+format = '[//](#a78bfa) [$user](#8e59c5) '
+disabled = false
+show_always = true
+
+[hostname]
+ssh_only = false
+format = '[//](#a78bfa) [$hostname](#e879f9 bold) '
+disabled = false
+
+[directory]
+truncation_length = 0
+truncation_symbol = '…/'
+home_symbol = '~'
+read_only = ' '
+format = '[//](#a78bfa) [$path](#f472b6 bold)[$read_only](#f472b6) '
+style = '#f472b6'
+
+[git_branch]
+symbol = ' '
+format = '[//](#a78bfa) [$symbol\[$branch\]](#e879f9 bold) '
+style = '#e879f9 bold'
+
+[git_status]
+disabled = true
+format = '[ $all_status $ahead_behind](#f472b6) '
+style = '#f472b6'
+
+[kubernetes]
+format = 'via [ $context\($namespace\)](#c084fc bold) '
+disabled = false
+
+[python]
+symbol = ''
+python_binary = ['./venv/bin/python', 'python', 'python3', 'python2']
+format = '[//](#a78bfa) [$symbol $pyenv_prefix($version )($virtualenv )](#e879f9 bold) '
+
+