diff --git a/ghostty/bettercrt.glsl b/ghostty/bettercrt.glsl
new file mode 100644
index 0000000..8f58b89
--- /dev/null
+++ b/ghostty/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/bloom.glsl b/ghostty/bloom.glsl
new file mode 100644
index 0000000..51b771c
--- /dev/null
+++ b/ghostty/bloom.glsl
@@ -0,0 +1,50 @@
+// 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;
+}
\ No newline at end of file
diff --git a/ghostty/config b/ghostty/config
new file mode 100644
index 0000000..e3694f8
--- /dev/null
+++ b/ghostty/config
@@ -0,0 +1,311 @@
+# keybind = global:cmd+grave_accent=toggle_quick_terminal
+
+# font settings
+font-size = 11
+bold-is-bright = true
+font-style-italic = Regular Italic
+
+# font options
+font-family = "SFMono Nerd Font"
+#font-family = "Berkeley Mono"
+# font-family = "Fragment Mono"
+# font-family = "MonaspiceNe NFM"
+
+# theme
+background = #000000
+# foreground = #c7c7c7
+unfocused-split-opacity = 1
+
+# shaders
+# custom-shader = ./starfield-colors.glsl
+# custom-shader-animation = always
+
+# window
+window-padding-x = 10
+window-padding-y = 10,3
+window-padding-color = extend
+window-inherit-working-directory = false
+background-opacity = 0.7
+background-blur = 20
+
+# size
+window-height = 45
+window-width = 111
+
+# shell
+link-url = true
+mouse-hide-while-typing = false
+cursor-style = underline
+shell-integration = detect
+shell-integration-features = no-cursor,no-sudo
+macos-titlebar-style = hidden
+macos-option-as-alt = true
+keybind = shift+enter=text:\r
+
+# colors
+cursor-color = #c7c7c7
+selection-background = #c1ddff
+cursor-invert-fg-bg = false
+selection-invert-fg-bg = false
+
+# palette
+
+# custom
+palette = 0=#1d1f21
+palette = 1=#c91b00
+palette = 2=#00c200
+palette = 3=#d29922
+palette = 4=#59a6ff
+palette = 5=#c930c7
+palette = 6=#00c5c7
+palette = 7=#c7c7c7
+palette = 8=#676767
+palette = 9=#ff6d67
+palette = 10=#5ff967
+palette = 11=#fefb67
+palette = 12=#6871ff
+palette = 13=#ff76ff
+palette = 14=#5ffdff
+palette = 15=#fffeff
+
+# default
+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
diff --git a/ghostty/crt.glsl b/ghostty/crt.glsl
new file mode 100644
index 0000000..31d1bec
--- /dev/null
+++ b/ghostty/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/crt2.glsl b/ghostty/crt2.glsl
new file mode 100644
index 0000000..c085e0b
--- /dev/null
+++ b/ghostty/crt2.glsl
@@ -0,0 +1,308 @@
+//==============================================================
+//
+// [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/glitchy.glsl b/ghostty/glitchy.glsl
new file mode 100644
index 0000000..603e3ec
--- /dev/null
+++ b/ghostty/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/glow-rgbsplit-twitchy.glsl b/ghostty/glow-rgbsplit-twitchy.glsl
new file mode 100644
index 0000000..9411e4e
--- /dev/null
+++ b/ghostty/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/in-game-crt.glsl b/ghostty/in-game-crt.glsl
new file mode 100644
index 0000000..34cfc62
--- /dev/null
+++ b/ghostty/in-game-crt.glsl
@@ -0,0 +1,304 @@
+// In-game CRT shader
+// Author: sarphiv
+// License: CC BY-NC-SA 4.0
+// Description:
+// Shader for ghostty that is focussed on being usable while looking like a stylized CRT terminal in a modern video game.
+// I know a tiny bit about shaders, and nothing about GLSL,
+// so this is a Frakenstein's monster combination of other shaders together with a lot of surgery.
+// On the bright side, i've cleaned up the body parts and surgery a lot.
+
+// Based on:
+// 1. https://gist.github.com/mitchellh/39d62186910dcc27cad097fed16eb882 (forces the choice of license)
+// 2. https://gist.github.com/qwerasd205/c3da6c610c8ffe17d6d2d3cc7068f17f
+// 3. https://gist.github.com/seanwcom/0fbe6b270aaa5f28823e053d3dbb14ca
+
+
+// Settings:
+// How straight the terminal is in each axis
+// (x, y) \in R^2 : x, y > 0
+#define CURVE 13.0, 11.0
+
+// How far apart the different colors are from each other
+// x \in R
+#define COLOR_FRINGING_SPREAD 1.0
+
+// How much the ghost images are spread out
+// x \in R : x >= 0
+#define GHOSTING_SPREAD 0.75
+// How visible ghost images are
+// x \in R : x >= 0
+#define GHOSTING_STRENGTH 1.0
+
+// How much of the non-linearly darkened colors are mixed in
+// [0, 1]
+#define DARKEN_MIX 0.4
+
+// How far in the vignette spreads
+// x \in R : x >= 0
+#define VIGNETTE_SPREAD 0.3
+// How bright the vignette is
+// x \in R : x >= 0
+#define VIGNETTE_BRIGHTNESS 6.4
+
+// Tint all colors
+// [0, 1]^3
+#define TINT 0.93, 1.00, 0.96
+
+// How visible the scan line effect is
+// NOTE: Technically these are not scan lines, but rather the lack of them
+// [0, 1]
+#define SCAN_LINES_STRENGTH 0.15
+// How bright the spaces between the lines are
+// [0, 1]
+#define SCAN_LINES_VARIANCE 0.35
+// Pixels per scan line effect
+// x \in R : x > 0
+#define SCAN_LINES_PERIOD 4.0
+
+// How visible the aperture grille is
+// x \in R : x >= 0
+#define APERTURE_GRILLE_STRENGTH 0.2
+// Pixels per aperture grille
+// x \in R : x > 0
+#define APERTURE_GRILLE_PERIOD 2.0
+
+// How much the screen flickers
+// x \in R : x >= 0
+#define FLICKER_STRENGTH 0.05
+// How fast the screen flickers
+// x \in R : x > 0
+#define FLICKER_FREQUENCY 15.0
+
+// How much noise is added to filled areas
+// [0, 1]
+#define NOISE_CONTENT_STRENGTH 0.15
+// How much noise is added everywhere
+// [0, 1]
+#define NOISE_UNIFORM_STRENGTH 0.03
+
+// How big the bloom is
+// x \in R : x >= 0
+#define BLOOM_SPREAD 8.0
+// How visible the bloom is
+// [0, 1]
+#define BLOOM_STRENGTH 0.04
+
+// How fast colors fade in and out
+// [0, 1]
+#define FADE_FACTOR 0.55
+
+
+
+// Disabled values for when the settings are not defined
+#ifndef COLOR_FRINGING_SPREAD
+#define COLOR_FRINGING_SPREAD 0.0
+#endif
+
+#if !defined(GHOSTING_SPREAD) || !defined(GHOSTING_STRENGTH)
+#undef GHOSTING_SPREAD
+#undef GHOSTING_STRENGTH
+#define GHOSTING_SPREAD 0.0
+#define GHOSTING_STRENGTH 0.0
+#endif
+
+#ifndef DARKEN_MIX
+#define DARKEN_MIX 0.0
+#endif
+
+#if !defined(VIGNETTE_SPREAD) || !defined(VIGNETTE_BRIGHTNESS)
+#undef VIGNETTE_SPREAD
+#undef VIGNETTE_BRIGHTNESS
+#define VIGNETTE_SPREAD 0.0
+#define VIGNETTE_BRIGHTNESS 1.0
+#endif
+
+#ifndef TINT
+#define TINT 1.00, 1.00, 1.00
+#endif
+
+#if !defined(SCAN_LINES_STRENGTH) || !defined(SCAN_LINES_VARIANCE) || !defined(SCAN_LINES_PERIOD)
+#undef SCAN_LINES_STRENGTH
+#undef SCAN_LINES_VARIANCE
+#undef SCAN_LINES_PERIOD
+#define SCAN_LINES_STRENGTH 0.0
+#define SCAN_LINES_VARIANCE 1.0
+#define SCAN_LINES_PERIOD 1.0
+#endif
+
+#if !defined(APERTURE_GRILLE_STRENGTH) || !defined(APERTURE_GRILLE_PERIOD)
+#undef APERTURE_GRILLE_STRENGTH
+#undef APERTURE_GRILLE_PERIOD
+#define APERTURE_GRILLE_STRENGTH 0.0
+#define APERTURE_GRILLE_PERIOD 1.0
+#endif
+
+#if !defined(FLICKER_STRENGTH) || !defined(FLICKER_FREQUENCY)
+#undef FLICKER_STRENGTH
+#undef FLICKER_FREQUENCY
+#define FLICKER_STRENGTH 0.0
+#define FLICKER_FREQUENCY 1.0
+#endif
+
+#if !defined(NOISE_CONTENT_STRENGTH) || !defined(NOISE_UNIFORM_STRENGTH)
+#undef NOISE_CONTENT_STRENGTH
+#undef NOISE_UNIFORM_STRENGTH
+#define NOISE_CONTENT_STRENGTH 0.0
+#define NOISE_UNIFORM_STRENGTH 0.0
+#endif
+
+#if !defined(BLOOM_SPREAD) || !defined(BLOOM_STRENGTH)
+#undef BLOOM_SPREAD
+#undef BLOOM_STRENGTH
+#define BLOOM_SPREAD 0.0
+#define BLOOM_STRENGTH 0.0
+#endif
+
+#ifndef FADE_FACTOR
+#define FADE_FACTOR 1.00
+#endif
+
+
+
+// Constants
+#define PI 3.1415926535897932384626433832795
+
+#ifdef BLOOM_SPREAD
+// Golden spiral samples used for bloom.
+// [x, y, weight] weight is inverse of distance.
+const vec3[24] bloom_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)
+};
+#endif
+
+
+
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ // Get texture coordinates
+ vec2 uv = fragCoord.xy / iResolution.xy;
+
+#ifdef CURVE
+ // Curve texture coordinates to mimic non-flat CRT monior
+ uv = (uv - 0.5) * 2.0;
+ uv.xy *= 1.0 + pow((abs(vec2(uv.y, uv.x)) / vec2(CURVE)), vec2(2.0));
+ uv = (uv / 2.0) + 0.5;
+#endif
+
+
+ // Retrieve colors from appropriate locations
+ fragColor.r = texture(iChannel0, vec2(uv.x + 0.0003 * COLOR_FRINGING_SPREAD, uv.y + 0.0003 * COLOR_FRINGING_SPREAD)).x;
+ fragColor.g = texture(iChannel0, vec2(uv.x + 0.0000 * COLOR_FRINGING_SPREAD, uv.y - 0.0006 * COLOR_FRINGING_SPREAD)).y;
+ fragColor.b = texture(iChannel0, vec2(uv.x - 0.0006 * COLOR_FRINGING_SPREAD, uv.y + 0.0000 * COLOR_FRINGING_SPREAD)).z;
+ fragColor.a = texture(iChannel0, uv).a;
+
+
+ // Add faint ghost images
+ fragColor.r += 0.04 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(+0.025, -0.027) + uv.xy).x;
+ fragColor.g += 0.02 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(-0.022, -0.020) + uv.xy).y;
+ fragColor.b += 0.04 * GHOSTING_STRENGTH * texture(iChannel0, GHOSTING_SPREAD * vec2(-0.020, -0.018) + uv.xy).z;
+
+
+ // Quadratically darken everything
+ fragColor.rgb = mix(fragColor.rgb, fragColor.rgb*fragColor.rgb, DARKEN_MIX);
+
+
+ // Vignette effect
+ fragColor.rgb *= VIGNETTE_BRIGHTNESS * pow(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y), VIGNETTE_SPREAD);
+
+
+ // Tint all colors
+ fragColor.rgb *= vec3(TINT);
+
+
+ // NOTE: At this point, RGB values may be above 1
+
+
+ // Add scan lines effect
+ fragColor.rgb *= mix(
+ 1.0,
+ SCAN_LINES_VARIANCE/2.0*(1.0 + sin(2*PI* uv.y * iResolution.y/SCAN_LINES_PERIOD)),
+ SCAN_LINES_STRENGTH
+ );
+
+
+ // Add aperture grille
+ int aperture_grille_step = int(8 * mod(fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD);
+ float aperture_grille_mask;
+
+ if (aperture_grille_step < 3)
+ aperture_grille_mask = 0.0;
+ else if (aperture_grille_step < 4)
+ aperture_grille_mask = mod(8*fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD;
+ else if (aperture_grille_step < 7)
+ aperture_grille_mask = 1.0;
+ else if (aperture_grille_step < 8)
+ aperture_grille_mask = mod(-8*fragCoord.x, APERTURE_GRILLE_PERIOD) / APERTURE_GRILLE_PERIOD;
+
+ fragColor.rgb *= 1.0 - APERTURE_GRILLE_STRENGTH*aperture_grille_mask;
+
+
+ // Add flicker
+ fragColor *= 1.0 - FLICKER_STRENGTH/2.0*(1.0 + sin(2*PI*FLICKER_FREQUENCY*iTime));
+
+
+ // Add noise
+ // NOTE: Hard-coded noise distributions
+ float noiseContent = smoothstep(0.4, 0.6, fract(sin(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y) * iTime * 4096.0) * 65536.0));
+ float noiseUniform = smoothstep(0.4, 0.6, fract(sin(uv.x * uv.y * (1.0-uv.x) * (1.0-uv.y) * iTime * 8192.0) * 65536.0));
+ fragColor.rgb *= clamp(noiseContent + 1.0 - NOISE_CONTENT_STRENGTH, 0.0, 1.0);
+ fragColor.rgb = clamp(fragColor.rgb + noiseUniform * NOISE_UNIFORM_STRENGTH, 0.0, 1.0);
+
+
+ // NOTE: At this point, RGB values are again within [0, 1]
+
+
+ // Remove output outside of screen bounds
+ if (uv.x < 0.0 || uv.x > 1.0)
+ fragColor.rgb *= 0.0;
+ if (uv.y < 0.0 || uv.y > 1.0)
+ fragColor.rgb *= 0.0;
+
+
+#ifdef BLOOM_SPREAD
+ // Add bloom
+ vec2 step = BLOOM_SPREAD * vec2(1.414) / iResolution.xy;
+
+ for (int i = 0; i < 24; i++) {
+ vec3 bloom_sample = bloom_samples[i];
+ vec4 neighbor = texture(iChannel0, uv + bloom_sample.xy * step);
+ float luminance = 0.299 * neighbor.r + 0.587 * neighbor.g + 0.114 * neighbor.b;
+
+ fragColor += luminance * bloom_sample.z * neighbor * BLOOM_STRENGTH;
+ }
+
+ fragColor = clamp(fragColor, 0.0, 1.0);
+#endif
+
+
+ // Add fade effect to smoothen out color transitions
+ // NOTE: May need to be iTime/iTimeDelta dependent
+ fragColor = vec4(FADE_FACTOR*fragColor.rgb, FADE_FACTOR);
+}
diff --git a/ghostty/mnoise.glsl b/ghostty/mnoise.glsl
new file mode 100644
index 0000000..a414a46
--- /dev/null
+++ b/ghostty/mnoise.glsl
@@ -0,0 +1,119 @@
+vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
+vec4 permute(vec4 x) { return mod289(((x * 34.0) + 10.0) * x); }
+vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
+float snoise(vec3 v) {
+ const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
+ const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
+
+ // First corner
+ vec3 i = floor(v + dot(v, C.yyy));
+ vec3 x0 = v - i + dot(i, C.xxx);
+
+ // Other corners
+ vec3 g = step(x0.yzx, x0.xyz);
+ vec3 l = 1.0 - g;
+ vec3 i1 = min(g.xyz, l.zxy);
+ vec3 i2 = max(g.xyz, l.zxy);
+
+ // x0 = x0 - 0.0 + 0.0 * C.xxx;
+ // x1 = x0 - i1 + 1.0 * C.xxx;
+ // x2 = x0 - i2 + 2.0 * C.xxx;
+ // x3 = x0 - 1.0 + 3.0 * C.xxx;
+ vec3 x1 = x0 - i1 + C.xxx;
+ vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
+ vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
+
+ // Permutations
+ i = mod289(i);
+ vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y +
+ vec4(0.0, i1.y, i2.y, 1.0)) +
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
+
+ // Gradients: 7x7 points over a square, mapped onto an octahedron.
+ // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+ float n_ = 0.142857142857; // 1.0/7.0
+ vec3 ns = n_ * D.wyz - D.xzx;
+
+ vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
+
+ vec4 x_ = floor(j * ns.z);
+ vec4 y_ = floor(j - 7.0 * x_); // mod(j,N)
+
+ vec4 x = x_ * ns.x + ns.yyyy;
+ vec4 y = y_ * ns.x + ns.yyyy;
+ vec4 h = 1.0 - abs(x) - abs(y);
+
+ vec4 b0 = vec4(x.xy, y.xy);
+ vec4 b1 = vec4(x.zw, y.zw);
+
+ // vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
+ // vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
+ vec4 s0 = floor(b0) * 2.0 + 1.0;
+ vec4 s1 = floor(b1) * 2.0 + 1.0;
+ vec4 sh = -step(h, vec4(0.0));
+
+ vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
+ vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
+
+ vec3 p0 = vec3(a0.xy, h.x);
+ vec3 p1 = vec3(a0.zw, h.y);
+ vec3 p2 = vec3(a1.xy, h.z);
+ vec3 p3 = vec3(a1.zw, h.w);
+
+ // Normalise gradients
+ vec4 norm =
+ taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
+ p0 *= norm.x;
+ p1 *= norm.y;
+ p2 *= norm.z;
+ p3 *= norm.w;
+
+ // Mix final noise value
+ vec4 m =
+ max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
+ m = m * m;
+ return 105.0 *
+ dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
+}
+
+float noise2D(vec2 uv) {
+ uvec2 pos = uvec2(floor(uv * 1000.));
+ return float((pos.x * 68657387u ^ pos.y * 361524851u + pos.x) % 890129u) *
+ (1.0 / 890128.0);
+}
+
+float roundRectSDF(vec2 center, vec2 size, float radius) {
+ return length(max(abs(center) - size + radius, 0.)) - radius;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord / iResolution.xy, sd = vec2(2.), sdh = vec2(1.);
+ vec4 ghosttyCol = texture(iChannel0, uv);
+ float ratio = iResolution.y / iResolution.x,
+ fw = max(fwidth(uv.x), fwidth(uv.y));
+
+ vec2 puv = floor(uv * vec2(60., 60. * ratio)) / 60.;
+ puv +=
+ (smoothstep(0., 0.7, noise2D(puv)) - 0.5) * 0.05 - vec2(0., iTime * 0.08);
+
+ uv = fract(vec2(uv.x, uv.y * ratio) * 10.);
+ float d = roundRectSDF((sd + 0.01) * (uv - .5), sdh, 0.075),
+ d2 = roundRectSDF((sd + 0.065) * (fract(uv * 6.) - .5), sdh, 0.2),
+ noiseTime = iTime * 0.03, noise = snoise(vec3(puv, noiseTime));
+
+ noise += snoise(vec3(puv * 1.1, noiseTime + 0.5)) + .1;
+ noise += snoise(vec3(puv * 2., noiseTime + 0.8));
+ noise = pow(noise, 2.);
+
+ vec3 col1 = vec3(0.), col2 = vec3(0.), col3 = vec3(0.07898),
+ col4 = vec3(0.089184),
+ fcol = mix(mix(mix(col1, col3, smoothstep(0.0, 0.3, noise)), col2,
+ smoothstep(0.0, 0.5, noise)),
+ col4, smoothstep(0.0, 1.0, noise));
+
+ fragColor = vec4(
+ ghosttyCol.rgb +
+ mix(col4, fcol, smoothstep(fw, -fw, d) * smoothstep(fw, -fw, d2)),
+ ghosttyCol.a);
+}
diff --git a/ghostty/retro-terminal.glsl b/ghostty/retro-terminal.glsl
new file mode 100644
index 0000000..c5f315a
--- /dev/null
+++ b/ghostty/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/starfield-colors.glsl b/ghostty/starfield-colors.glsl
new file mode 100644
index 0000000..cf125a9
--- /dev/null
+++ b/ghostty/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);
+
+}