├── .gitignore ├── Balatro ├── README.md ├── m6x11_mono.ttf └── shaders ├── balatro.glsl ├── tft.glsl └── wiggle.glsl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE* -------------------------------------------------------------------------------- /Balatro: -------------------------------------------------------------------------------- 1 | background ="#000000" 2 | custom-shader = shaders/wiggle.glsl 3 | custom-shader = shaders/balatro.glsl 4 | custom-shader = shaders/tft.glsl 5 | font-size = "24" 6 | font-family = "m6x11" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ghostty-balatro-theme 2 | 3 | A terminal theme for ghostty that replicates the style of balatro 4 | 5 | 6 | https://private-user-images.githubusercontent.com/47284813/407983200-3cb58c65-361c-472b-b3c8-825dcd047661.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzgyMDc3MTcsIm5iZiI6MTczODIwNzQxNywicGF0aCI6Ii80NzI4NDgxMy80MDc5ODMyMDAtM2NiNThjNjUtMzYxYy00NzJiLWIzYzgtODI1ZGNkMDQ3NjYxLm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAxMzAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMTMwVDAzMjMzN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTk2YzY3MDQ3N2I4ZmQzZjM5YmNhODM1YWU4OWExMzk4NGU5ZmM5MDNkNzlkMTVlOTVmMmU1ZmYzMTIzYTU5ZjImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.iCSP00MyobX_EWJmyCqmZ-mk5ByWWOZ5VYnIhJW_b8c 7 | 8 | # Setup: 9 | 10 | - Add the theme to your themes folder 11 | - Ex: .config/ghostty/themes/Balatro 12 | - Add the shaders folder to the root of your ghostty config folder 13 | - Ex: .config/ghostty/shaders 14 | - You can also install the m6x11_mono.tff font to use as your font 15 | -------------------------------------------------------------------------------- /m6x11_mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mynameisgump/ghostty-balatro-theme/e38cbb0b8b10eb3727f7ea098860dbee63576161/m6x11_mono.ttf -------------------------------------------------------------------------------- /shaders/balatro.glsl: -------------------------------------------------------------------------------- 1 | // Taken from https://www.shadertoy.com/view/XXjGDt 2 | // Modified to include a grey overlay, and to also 3 | // chromakey out the black background 4 | #define PIXEL_SIZE_FAC 700.0 5 | #define SPIN_EASE 0.5 6 | #define colour_2 vec4(0.0, 156.0 / 255.0, 1.0, 1.0) 7 | #define colour_1 vec4(0.85, 0.2, 0.2, 1.0) 8 | #define colour_3 vec4(0.0, 0.0, 0.0, 1.0) 9 | #define spin_amount 0.7 10 | #define contrast 1.5 11 | 12 | // Define black background color 13 | const vec3 BACKGROUND_COLOR = vec3(0.0, 0.0, 0.0); 14 | const float TOLERANCE = 0.01; 15 | 16 | const vec4 greyOverlay = vec4(40.0 / 255.0, 40.0 / 255.0, 40.0 / 255.0, 0.7); // Transparent grey 17 | 18 | 19 | void mainImage(out vec4 fragColor, in vec2 fragCoord) 20 | { 21 | vec4 inputColor = texture(iChannel0, fragCoord / iResolution.xy); // Sample current screen color 22 | 23 | // Check if the pixel matches the background color (within tolerance) 24 | if (distance(inputColor.rgb, BACKGROUND_COLOR) < TOLERANCE) 25 | { 26 | // Apply shader effect only to black background 27 | float pixel_size = length(iResolution.xy) / PIXEL_SIZE_FAC; 28 | vec2 uv = (floor(fragCoord.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * iResolution.xy) / length(iResolution.xy) - vec2(0.0, 0.0); 29 | float uv_len = length(uv); 30 | 31 | float speed = (iTime * SPIN_EASE * 0.1) + 302.2; 32 | float new_pixel_angle = (atan(uv.y, uv.x)) + speed - SPIN_EASE * 20.0 * (1.0 * spin_amount * uv_len + (1.0 - 1.0 * spin_amount)); 33 | vec2 mid = (iResolution.xy / length(iResolution.xy)) / 2.0; 34 | uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid); 35 | 36 | uv *= 30.0; 37 | speed = iTime * (1.0); 38 | vec2 uv2 = vec2(uv.x + uv.y); 39 | 40 | for (int i = 0; i < 5; i++) 41 | { 42 | uv2 += uv + cos(length(uv)); 43 | uv += 0.5 * vec2(cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121), sin(uv2.x - 0.113 * speed)); 44 | uv -= 1.0 * cos(uv.x + uv.y) - 1.0 * sin(uv.x * 0.711 - uv.y); 45 | } 46 | 47 | float contrast_mod = (0.25 * contrast + 0.5 * spin_amount + 1.2); 48 | float paint_res = min(2.0, max(0.0, length(uv) * (0.035) * contrast_mod)); 49 | float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res)); 50 | float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res)); 51 | float c3p = 1.0 - min(1.0, c1p + c2p); 52 | 53 | vec4 ret_col = (0.3 / contrast) * colour_1 + (1.0 - 0.3 / contrast) * (colour_1 * c1p + colour_2 * c2p + vec4(c3p * colour_3.rgb, c3p * colour_1.a)) + 0.3 * max(c1p * 5.0 - 4.0, 0.0) + 0.4 * max(c2p * 5.0 - 4.0, 0.0); 54 | 55 | // Add transparent grey overlay if within bounds 56 | vec2 overlayMin = vec2(0, 0); // Top-left bound (normalized coordinates) 57 | vec2 overlayMax = vec2(1, 1); // Bottom-right bound (normalized coordinates) 58 | bool insideOverlay = fragCoord.x / iResolution.x > overlayMin.x && fragCoord.x / iResolution.x < overlayMax.x && 59 | fragCoord.y / iResolution.y > overlayMin.y && fragCoord.y / iResolution.y < overlayMax.y; 60 | 61 | // Apply overlay if within the square area 62 | fragColor = insideOverlay ? mix(ret_col, greyOverlay, greyOverlay.a) : ret_col; 63 | 64 | // fragColor = ret_col; 65 | 66 | } 67 | else 68 | { 69 | // Keep the original color for non-black pixels 70 | fragColor = inputColor; 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /shaders/tft.glsl: -------------------------------------------------------------------------------- 1 | // User tweakable setting: how many faux pixels tall the screen should be 2 | const float pseudoPixelRows = 320.0; // Higher = smaller pixels 3 | const float strength = 1; 4 | 5 | void _scanline(inout vec3 color, vec2 fragCoord, float pixelSize) 6 | { 7 | float scanline = step(1.2, mod(fragCoord.y, pixelSize)); 8 | float grille = step(1.2, mod(fragCoord.x, pixelSize)); 9 | color *= max(1.0 - strength, scanline * grille); 10 | } 11 | 12 | void mainImage(out vec4 fragColor, in vec2 fragCoord) 13 | { 14 | vec2 uv = fragCoord / iResolution.xy; 15 | vec3 color = texture(iChannel0, uv).rgb; 16 | 17 | // Compute pixelSize based on screen height, but keep it integer 18 | float pixelSize = floor(iResolution.y / pseudoPixelRows); 19 | pixelSize = max(1.0, pixelSize); // avoid zero division 20 | 21 | _scanline(color, fragCoord, pixelSize); 22 | 23 | fragColor = vec4(color, 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /shaders/wiggle.glsl: -------------------------------------------------------------------------------- 1 | // Chatgpt generate wiggle shader 2 | #define TAU 6.28318530718 3 | 4 | void mainImage( out vec4 fragColor, in vec2 fragCoord ) 5 | { 6 | // Get the screen space UV coordinates 7 | vec2 uv = fragCoord.xy / iResolution.xy; 8 | 9 | // Set the amount of wiggle based on time 10 | float time = iTime * 0.5; // Adjust this value to change the speed of the wiggle 11 | float wiggleStrength = 0.003; // Adjust this value to change how much the screen wiggles 12 | 13 | // Apply a sine-cosine perturbation to the UV coordinates 14 | vec2 wiggle = vec2(sin(time + uv.y * TAU), cos(time + uv.x * TAU)) * wiggleStrength; 15 | 16 | // Apply the wiggle to the UV coordinates 17 | uv += wiggle; 18 | 19 | // Sample the texture at the new, perturbed UV coordinates 20 | fragColor = texture(iChannel0, uv); 21 | } 22 | 23 | --------------------------------------------------------------------------------