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
parent
2d7336c593
commit
ace372780d
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,16 @@ window_padding_width 4
|
||||||
scrollback_lines 20000
|
scrollback_lines 20000
|
||||||
background_opacity 0.4
|
background_opacity 0.4
|
||||||
background_blur 1
|
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
|
editor nvim
|
||||||
confirm_os_window_close 0
|
confirm_os_window_close 0
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Loading…
Reference in New Issue