feat(hyprdrive): holographic glow for kitty + new Ghostty CyberQueer config

kitty: add a seamlessly-tiled scanlines+noise+ambient-bloom PNG as
background_image, giving kitty a static approximation of the suite's
HologramOverlay treatment since it has no per-frame draw hook or shader
support to do real bloom.

ghostty: port the CyberQueer theme (palette, cursor/selection colors)
to a new Ghostty config, with a real per-pixel custom-shader instead of
a static image — color-preserving bloom, procedural scanlines/grain, an
occasional analog glitch/roll burst (tear + vertical roll + chromatic
aberration), and a one-time hologram boot intro that resolves out of
colored static via a downward sweep. Window decorations and the tab bar
are disabled to match kitty's borderless look under Hyprland.

Not wired up anywhere yet — no keybind or $TERMINAL changes point at
Ghostty. This is just the config, ready to test.
main
Amir Alexander Abdelbaki 2026-07-20 09:46:14 +02:00
parent 2d7336c593
commit ace372780d
4 changed files with 276 additions and 0 deletions

View File

@ -0,0 +1,64 @@
# CyberQueer — Ghostty config
# Ported from ../kitty/{kitty.conf,themes/cyberqueer.conf}. Palette matches
# ~/Dotfiles/colors.conf (COLOR_TEXT/BG/HIGHLIGHT/DARK/RED) plus the same
# neon green/yellow/magenta/cyan tints used in the kitty theme so ANSI
# output looks identical across both terminals. Kitty could only fake a
# glow with a static background image (../kitty/scanlines.png); here it's
# a real per-frame bloom shader — see shaders/cyberqueer-glow.glsl.
#
# NOT wired up as the active terminal yet: no keybinds or $TERMINAL changes
# point at Ghostty. This file just needs `ghostty --config-file=<this path>`
# (or deploying to ~/.config/ghostty/config) to try it.
font-family = Agave Nerd Font
font-size = 13
cursor-style = bar
cursor-style-blink = false
shell-integration-features = no-cursor
background-opacity = 0.4
background-blur = true
window-padding-x = 4
window-padding-y = 4
scrollback-limit = 10485760
confirm-close-surface = false
# No titlebar, no tab bar — Hyprland handles window placement/borders itself
# (matches kitty's borderless look under this compositor). Note: an old
# Ghostty bug (ghostty-org/ghostty#3178, fixed in 1.0.1) made this exact
# Arch+Hyprland combo render as a single flat color with gtk-titlebar=false;
# using window-decoration=none instead of gtk-titlebar avoids that specific
# combo, but if the "flat pane, no text" symptom ever comes back, this is
# the first place to check by reverting.
window-decoration = none
window-show-tab-bar = never
custom-shader = shaders/cyberqueer-glow.glsl
custom-shader-animation = always
foreground = D6ABAB
background = 1A1A1A
selection-foreground = E40046
selection-background = 5018DD
cursor-color = E40046
cursor-text = 5018DD
split-divider-color = 5018DD
palette = 0=1A1A1A
palette = 1=E40046
palette = 2=33E6A0
palette = 3=E8B23D
palette = 4=5018DD
palette = 5=A6178C
palette = 6=34C6C6
palette = 7=D6ABAB
palette = 8=4B3A55
palette = 9=F50505
palette = 10=5CFFC2
palette = 11=FFD873
palette = 12=8C6FFF
palette = 13=FF5FC0
palette = 14=6FF5F0
palette = 15=F2DEDE

View File

@ -0,0 +1,202 @@
// CyberQueer glow shader for Ghostty's custom-shader hook.
// Real per-pixel bloom (unlike kitty, which can only fake this with a static
// background image — see ../../kitty/scanlines.png). Deliberately
// color-preserving: it blooms whatever color a glyph already is, so the
// suite's violet/crimson/neon ANSI palette blooms in its own hues instead of
// a single hardcoded tint.
//
// Only one custom-shader reliably renders on Linux/OpenGL (stacking multiple
// silently drops all but the last), so bloom + scanlines + grain + glitch +
// the boot intro all live here as a single pass rather than several files.
//
// Four effects, composited in order:
// 1. occasional glitch/roll burst (analog-signal tear + vertical roll +
// chromatic aberration), ported from the technique in
// github.com/0xhckr/ghostty-shaders/blob/main/glitchy.glsl
// 2. bloom (existing, sampled post-glitch-warp)
// 3. scanlines + ambient grain (existing)
// 4. a one-time hologram boot intro: the picture resolves out of colored
// static via a downward sweep over the first ~1.8s of iTime.
// ---------- shared noise helpers (glitch §1 + intro §4) ----------
// SS: a windowed pulse — 0 outside [a,b], rising then falling inside it.
#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.0 / float(0xffffffffu))
// Hash by David Hoskins — reused from glitchy.glsl for the glitch burst's
// analog distortion and the intro's static field.
vec3 hash33(vec3 p) {
uvec3 q = uvec3(ivec3(p)) * UI3;
q = (q.x ^ q.y ^ q.z) * UI3;
return -1.0 + 2.0 * vec3(q) * UIF;
}
// Gradient noise by iq — continuous in space and time, so animating it
// reads as organic shimmer rather than a hard per-frame strobe (deliberate:
// this drives a full-window effect, so avoiding flicker matters).
float gnoise(vec3 x) {
vec3 p = floor(x);
vec3 w = fract(x);
vec3 u = w * w * w * (w * (w * 6.0 - 15.0) + 10.0);
vec3 ga = hash33(p + vec3(0.0, 0.0, 0.0));
vec3 gb = hash33(p + vec3(1.0, 0.0, 0.0));
vec3 gc = hash33(p + vec3(0.0, 1.0, 0.0));
vec3 gd = hash33(p + vec3(1.0, 1.0, 0.0));
vec3 ge = hash33(p + vec3(0.0, 0.0, 1.0));
vec3 gf = hash33(p + vec3(1.0, 0.0, 1.0));
vec3 gg = hash33(p + vec3(0.0, 1.0, 1.0));
vec3 gh = hash33(p + vec3(1.0, 1.0, 1.0));
float va = dot(ga, w - vec3(0.0, 0.0, 0.0));
float vb = dot(gb, w - vec3(1.0, 0.0, 0.0));
float vc = dot(gc, w - vec3(0.0, 1.0, 0.0));
float vd = dot(gd, w - vec3(1.0, 1.0, 0.0));
float ve = dot(ge, w - vec3(0.0, 0.0, 1.0));
float vf = dot(gf, w - vec3(1.0, 0.0, 1.0));
float vg = dot(gg, w - vec3(0.0, 1.0, 1.0));
float vh = dot(gh, w - vec3(1.0, 1.0, 1.0));
return 2.0 * (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));
}
float gnoise01(vec3 x) {
return 0.5 + 0.5 * gnoise(x);
}
// Golden-spiral sample offsets + inverse-distance weights for the bloom,
// reused verbatim from the community bloom kernel (0xhckr/ghostty-shaders).
const vec3[24] samples = vec3[24](
vec3(0.1693761725038636, 0.9855514761735895, 1.0),
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;
}
// Cheap 2D hash for the ambient grain speckles — no texture lookup needed.
float hash(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
// How often (seconds) an occasional glitch/roll burst fires, and what
// fraction of that cycle it's actually active for — "occasional" means
// rare and brief, not a constant tic.
#define GLITCH_DURATION 18.0
#define GLITCH_AMT 0.045
// How long the hologram takes to resolve out of static when it first boots.
#define INTRO_DURATION 1.8
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
float t = iTime;
// --- 1. occasional glitch/roll burst ---
// glitchAmount is ~0 almost all the time and spikes for under a second
// once every GLITCH_DURATION seconds.
float glitchAmount = SS(GLITCH_DURATION * 0.002, GLITCH_DURATION * GLITCH_AMT, mod(t, GLITCH_DURATION));
float rowY = fragCoord.y;
float tear = gnoise(vec3(0.0, rowY * 0.01, t * 500.0)) * (glitchAmount * 0.03 + 0.0015);
float roll = glitchAmount * gnoise(vec3(t * 3.0, 0.0, 0.0)) * 0.05;
vec2 guv = uv + vec2(tear, roll);
guv.y = fract(guv.y); // vertical roll wraps top/bottom, like a CRT losing vertical hold
vec2 caOffset = vec2(2.0 / iResolution.x, 0.0) * (1.0 + glitchAmount * 3.0);
vec4 color;
color.r = texture(iChannel0, guv + caOffset).r;
color.g = texture(iChannel0, guv).g;
color.b = texture(iChannel0, guv - caOffset).b;
color.a = texture(iChannel0, guv).a;
// --- 2. bloom: color-preserving glow around bright glyphs ---
vec2 bstep = vec2(1.414) / iResolution.xy;
for (int i = 0; i < 24; i++) {
vec3 s = samples[i];
vec4 c = texture(iChannel0, guv + s.xy * bstep);
float l = lum(c);
if (l > 0.25) {
color += l * s.z * c * 0.15;
}
}
// burst noise: a brief flash of static riding on top of the tear/roll
float burst = hash33(vec3(fragCoord, mod(float(iFrame), 1000.0))).r;
color.rgb += burst * glitchAmount * 0.35;
// --- 3. scanlines: a hard-edged dark line every 3rd physical row ---
float scan = mod(fragCoord.y, 3.0) < 1.0 ? 0.90 : 1.0;
color.rgb *= scan;
// holographic grain: sparse, low-alpha, slowly time-varying speckles in
// the theme's accent/violet/danger hues. Update rate capped at ~8Hz and
// intensity kept low deliberately — this sits behind text all day, so it
// should read as ambient shimmer, not a strobe.
vec2 grainCell = floor(fragCoord.xy / 2.0) + floor(iTime * 8.0);
float n = hash(grainCell);
if (n > 0.985) {
vec3 speck = n > 0.9925 ? vec3(0.894, 0.0, 0.271) // accent E40046
: n > 0.99 ? vec3(0.961, 0.020, 0.020) // danger F50505
: vec3(0.314, 0.094, 0.867); // violet 5018DD
color.rgb += speck * 0.10;
}
// --- 4. hologram boot intro: resolve out of colored static over
// INTRO_DURATION seconds via a downward sweep, then never run again for
// the life of the shader (t only grows). Uses gnoise (continuous) rather
// than a per-frame hash flip, so the static shimmers instead of strobing.
float introProgress = clamp(t / INTRO_DURATION, 0.0, 1.0);
float sweepFrac = introProgress * 1.15 - 0.05;
float resolved = 1.0 - smoothstep(sweepFrac - 0.05, sweepFrac + 0.05, uv.y);
float n1 = gnoise01(vec3(fragCoord * 0.08, t * 6.0));
float n2 = gnoise01(vec3(fragCoord * 0.08 + 19.0, t * 6.0));
vec3 violet = vec3(0.314, 0.094, 0.867);
vec3 accent = vec3(0.894, 0.0, 0.271);
vec3 danger = vec3(0.961, 0.020, 0.020);
vec3 introField = mix(violet, accent, n1);
introField = mix(introField, danger, smoothstep(0.75, 1.0, n2) * 0.5);
introField *= (0.18 + 0.22 * n2); // keep it dim — ambient static, not a flash
float sweepBand = 1.0 - smoothstep(0.0, 0.12, abs(uv.y - sweepFrac));
vec3 sweepGlow = violet * sweepBand * 1.2;
vec3 finalRGB = mix(introField, color.rgb, resolved) + sweepGlow;
fragColor = vec4(finalRGB, color.a);
}

View File

@ -11,6 +11,16 @@ window_padding_width 4
scrollback_lines 20000
background_opacity 0.4
background_blur 1
# Holographic scanlines + noise grain + soft ambient violet/crimson bloom,
# matching the suite's HologramOverlay treatment (orbit-menu/astro-menu/
# station-bar/beacon) — a static tiled equivalent since kitty has no
# per-frame draw hook to animate it, and no shader hook to bloom glyphs
# directly, so the "glow" comes from ambient light behind the text instead.
background_image scanlines.png
background_image_layout tiled
background_tint 0.3
editor nvim
confirm_os_window_close 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB