par-term 0.37.1

Cross-platform GPU-accelerated terminal emulator with inline graphics support (Sixel, iTerm2, Kitty)
/*! par-term shader metadata
name: jellyfish
author: null
description: null
version: 1.0.0
defaults:
  animation_speed: 0.8
  channel0: textures/wallpaper/MagicMushrooms.png
  channel1: null
  channel2: null
  channel3: null
  cubemap: null
  cubemap_enabled: false
  use_background_as_channel0: null
  uniforms:
    iBackgroundDim: 0.55
    iCausticStrength: 1.0
    iGlowStrength: 1.0
    iJellyBrightness: 1.0
    iJellyScale: 1.0
    iJellyTint: '#ffffff'
    iParticleCount: 20
    iSwimSpeed: 1.0
    iVignetteStrength: 1.0
*/

// Animated procedural jellyfish — dark water, neon blue/purple
// Depth layers, variable tentacle count/length, wide size variation

// control slider min=0 max=3 step=0.01 label="Swim Speed"
uniform float iSwimSpeed;
// control slider min=0.5 max=2 step=0.01 label="Jelly Scale"
uniform float iJellyScale;
// control slider min=0 max=2.5 step=0.01 label="Jelly Brightness"
uniform float iJellyBrightness;
// control slider min=0 max=3 step=0.01 label="Glow Strength"
uniform float iGlowStrength;
// control slider min=0 max=1 step=0.01 label="Background Dim"
uniform float iBackgroundDim;
// control slider min=0 max=3 step=0.01 label="Caustic Strength"
uniform float iCausticStrength;
// control int min=0 max=40 step=1 label="Particle Count"
uniform int iParticleCount;
// control slider min=0 max=2 step=0.01 label="Vignette Strength"
uniform float iVignetteStrength;
// control color label="Jelly Tint"
uniform vec3 iJellyTint;

float hash(float n) { return fract(sin(n) * 43758.5453); }

float hash2(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }

float noise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash2(i), b = hash2(i + vec2(1,0));
    float c = hash2(i + vec2(0,1)), d = hash2(i + vec2(1,1));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

// Smooth minimum — negative inside both shapes, smooth junction
float smin(float a, float b, float k) {
    float hh = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
    return mix(b, a, hh) - k*hh*(1.0-hh);
}

// Jellyfish bell SDF — upper dome open at bottom
// Uses smooth intersection (smax = -smin(-a,-b,k)) so the opening edge is
// rounded rather than a flat hard clip, eliminating the visible seam.
float bellSDF(vec2 p, float r, float h) {
    vec2 q = vec2(p.x / r, p.y / h);
    float d    = (length(q) - 1.0) * min(r, h);
    float clip = -(p.y + h * 0.08) * 0.5;
    float k    = h * 0.45; // smoothness — larger = rounder opening
    return -smin(-d, -clip, k); // smooth max = smooth intersection
}

// Single wavy tentacle — returns vec2(sdf, signed_x_from_axis)
// signed_x < 0 = left of axis, > 0 = right of axis (used for 3D horizontal lighting)
vec2 tentacleInfo(vec2 p, float bx, float s1, float s2, float maxLen) {
    float y = -p.y;
    if (y < 0.0) return vec2(1e6, 0.0);
    float tLen = maxLen * (0.70 + hash(s1 * 0.7) * 0.55);
    if (y > tLen) return vec2(1e6, 0.0);
    float t = y / tLen;
    float time = iTime * iSwimSpeed;
    float wave = sin(y * 22.0 + time * 2.8 + s1) * 0.010 * t
               + sin(y * 14.0 - time * 1.9 + s2)  * 0.007 * t
               + cos(y *  8.0 + time * 1.2 + s1 * 0.5) * 0.005 * t;
    float thick = mix(0.006, 0.0007, t * t);
    float dx = p.x - bx - wave;
    return vec2(abs(dx) - thick, dx);
}

// Returns additive (premultiplied) color for one jellyfish.
// depth: 0.0 = foreground (full size/brightness), 1.0 = background (small/dark)
vec3 drawJelly(vec2 p, float seed, float depth) {
    float depthScale = mix(1.0, 0.38, depth);
    float time = iTime * iSwimSpeed;

    // Wider size variation before depth scaling
    float r  = (0.040 + hash(seed * 3.1) * 0.090) * depthScale;
    float h  = r * 0.60;

    // Breathing pulse — nonlinear: quick snap contraction, slow relaxation
    float pulseFreq = 1.8 + hash(seed * 5.7) * 1.2;
    float rawPulse = 0.5 + 0.5 * sin(time * pulseFreq + seed * 6.28);
    float pulse = pow(rawPulse, 0.35); // spends most time relaxed, contracts sharply
    float br = r * 0.9 * (1.0 - 0.28 * (1.0 - pulse)); // narrows on contraction
    float bh = h * (1.0 + 0.30 * (1.0 - pulse));        // elongates on contraction

    // Neon blue / purple palette
    float hue = hash(seed * 7.3);
    vec3 colA, colB;
    if (hue < 0.33) {
        colA = vec3(0.05, 0.30, 1.00); // neon blue
        colB = vec3(0.50, 0.05, 0.95); // electric purple
    } else if (hue < 0.66) {
        colA = vec3(0.50, 0.05, 0.95); // electric purple
        colB = vec3(0.00, 0.70, 1.00); // cyan-blue
    } else {
        colA = vec3(0.00, 0.70, 1.00); // cyan-blue
        colB = vec3(0.15, 0.15, 0.90); // deep blue
    }
    vec3 col      = mix(colA, colB, hash(seed * 2.9)) * iJellyTint;
    vec3 colBright = col + vec3(0.28, 0.22, 0.12);

    // Bell SDF
    float bd = bellSDF(p, br, bh);

    // Glowing inner core
    float coreDist = length(p - vec2(0.0, bh * 0.25)) / (r * 0.40);
    float core = exp(-coreDist * coreDist * 2.5) * 0.85;

    // Variable tentacle count: 3–9 per jellyfish
    int   nTent   = 3 + int(hash(seed * 9.3) * 7.0);
    // Per-jellyfish base tentacle length with wide variation, scaled by depth
    float tentLen = (0.10 + hash(seed * 6.3) * 0.25) * depthScale;

    float tentD = 1e6;
    float tentXSigned = 0.0; // signed x-offset from nearest tentacle axis
    vec2  tp = p + vec2(0.0, bh * 0.08);
    for (int k = 0; k < 9; k++) {
        if (k >= nTent) break;
        float fk  = float(k);
        float fnT = float(nTent);
        float bx  = (fk / max(fnT - 1.0, 1.0) - 0.5) * 2.0 * br * 0.88;
        vec2 info = tentacleInfo(tp, bx, fk + seed * 7.1, fk * seed * 1.3 + 0.5, tentLen);
        if (info.x < tentD) {
            tentD = info.x;
            tentXSigned = info.y;
        }
    }

    // Tentacle gradient: bright/white at root → deep saturated at tips
    float tentDepth = clamp(-(p.y + bh * 0.08) / max(tentLen, 0.01), 0.0, 1.0);
    vec3 tentColRoot = colBright;
    vec3 tentColTip  = col * vec3(0.5, 0.4, 0.7);
    vec3 tentCol = mix(tentColRoot, tentColTip, tentDepth * tentDepth);

    // --- Accumulate additive contributions ---
    vec3 result = vec3(0.0);
    float glowControl = max(iGlowStrength, 0.0);

    // Outer glow halo
    float glow = exp(-max(0.0, bd) / (r * 0.65));
    result += col * glow * 0.22 * glowControl;

    // Bell translucent fill
    float bellFill = smoothstep(0.005, -0.001, bd);
    result += col * bellFill * 0.42;

    // Bright bioluminescent core
    result += colBright * bellFill * core * glowControl;

    // Rim highlight — masked off at the bell opening so no hard edge there
    float rimMask = smoothstep(-bh * 0.05, bh * 0.30, p.y);
    float rim = smoothstep(0.010, 0.001, abs(bd)) * rimMask;
    result += (col + vec3(0.4)) * rim * 0.45;

    // Skirt — smooth fan bridging bell to tentacle roots
    float skirtDepth = -(p.y + bh * 0.08);
    float skirtMaxD  = br * 0.55;
    float skirtFade  = clamp(1.0 - skirtDepth / skirtMaxD, 0.0, 1.0);
    float skirtWidth = br * (0.95 - 0.6 * (skirtDepth / skirtMaxD));
    float skirtShape = smoothstep(1.0, 0.3, abs(p.x) / max(skirtWidth, 0.001));
    float skirt = skirtShape * skirtFade * skirtFade
                * smoothstep(-bh * 0.02, bh * 0.025, skirtDepth);
    result += colBright * skirt * 0.42; // boosted to fill the junction seam

    // Tentacle glow + fill
    float tentGlow = exp(-max(0.0, tentD) / 0.009);
    result += tentCol * tentGlow * 0.14 * glowControl;

    // 3D horizontal lighting gradient: cylindrical cross-section lit from upper-left
    // normalX: -1 = left edge, 0 = center axis, +1 = right edge
    float tentHalfW = 0.0045;
    float normalX = clamp(tentXSigned / tentHalfW, -1.0, 1.0);
    // Roundness from unsigned distance: center bright, edges dark
    float u = clamp(-tentD / tentHalfW, 0.0, 1.0);
    float roundness = sqrt(max(0.0, 2.0*u - u*u));
    // Diffuse: left face fully illuminated, right face in shadow
    float diffuse = max(0.0, -normalX);
    // Specular highlight: bright spot on the lit (left) side
    float specPeak = normalX + 0.50;
    float specular = exp(-specPeak * specPeak * 8.0) * 0.50;
    float lighting = 0.25 + 0.55 * diffuse + 0.40 * roundness + specular;
    vec3 tentColRound = tentCol * clamp(lighting, 0.1, 1.8);

    float tentFill = smoothstep(0.0022, -0.001, tentD);
    result += tentColRound * tentFill * 0.88;

    // Depth darkening: background jellyfish are much dimmer
    return result * mix(0.80, 0.18, depth) * iJellyBrightness;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv  = fragCoord / iResolution.xy;
    float asp = iResolution.x / iResolution.y;
    // Flip Y: uv.y=0 is top in par-term
    vec2 p   = vec2((uv.x - 0.5) * asp, 0.5 - uv.y);

    float t = iTime * iSwimSpeed;
    float jellyScale = max(iJellyScale, 0.01);

    // ── Background ─────────────────────────────────────────────────────────
    // Use iChannel0 image if configured, otherwise procedural dark water.
    bool hasBgTex = iChannelResolution[0].x > 1.0 && iChannelResolution[0].y > 1.0;

    vec3 bg;
    if (hasBgTex) {
        bg = texture(iChannel0, uv).rgb;
        // Darken it so jellyfish remain the visual focus
        bg *= iBackgroundDim;
    } else {
        // Procedural deep water
        vec3 deep    = vec3(0.003, 0.007, 0.045);
        vec3 surface = vec3(0.008, 0.022, 0.090);
        // uv.y=0 is top (surface), uv.y=1 is bottom (deep)
        bg = mix(surface, deep, pow(uv.y, 0.70));

        // Caustic light shimmer from above (water-specific, skip with image bg)
        float caust = 0.0;
        for (int i = 0; i < 3; i++) {
            float fi = float(i);
            vec2 cn = uv * (2.5 + fi * 1.3)
                    + vec2(sin(t * 0.18 + fi * 2.3), cos(t * 0.13 + fi * 1.7)) * 0.35;
            caust += abs(noise(cn) - 0.5);
        }
        // Caustic strongest near surface (top = uv.y near 0)
        bg += vec3(0.003, 0.007, 0.026) * (caust / 3.0) * (1.0 - uv.y) * 1.6 * iCausticStrength;
    }

    // Bioluminescent floating particles rising slowly
    for (int i = 0; i < 40; i++) {
        if (i >= iParticleCount) break;
        float fi  = float(i);
        float px  = (hash(fi * 3.71) - 0.5) * asp;
        float spd = 0.012 + hash(fi * 9.37) * 0.022;
        float py  = fract(hash(fi * 5.13) + t * spd) - 0.5;
        float pd  = length(p - vec2(px, py));
        float ps  = 0.003 + hash(fi * 2.91) * 0.003;
        float tw  = 0.3 + 0.7 * sin(t * 2.1 + fi * 1.73);
        vec3  pc  = mix(vec3(0.10, 0.38, 0.90), vec3(0.50, 0.10, 0.90), hash(fi * 7.33));
        bg += pc * smoothstep(ps * 2.5, 0.0, pd) * tw * 0.30;
    }

    // ── Jellyfish — background layer first, then foreground ───────────────
    vec3 jellies = vec3(0.0);

    // Background: 6 jellyfish, smaller, darker, slower — appear further away
    for (int i = 0; i < 6; i++) {
        float fi   = float(i);
        float seed = fi + 20.0;                          // distinct seed range
        float depth = 0.60 + hash(fi * 3.37) * 0.35;   // 0.60–0.95

        float ySpeed = 0.010 + hash(fi * 6.83) * 0.014;
        float ySign  = (hash(fi * 13.71) > 0.35) ? 1.0 : -1.0; // 65% up, 35% down
        float xSpeed = (hash(fi * 19.31) - 0.5) * ySpeed * 0.55; // independent sideways drift
        float phase  = hash(fi * 11.17);
        float xPhase = hash(fi * 5.51);
        float py     = fract(phase  + t * ySpeed * ySign) * 1.60 - 0.75;
        float xPos   = (fract(xPhase + t * xSpeed) - 0.5) * asp * 1.84;
        float xWobble = sin(t * 0.17 + fi * 2.71) * 0.025 * asp;

        jellies += drawJelly((p - vec2(xPos + xWobble, py)) / jellyScale, seed, depth);
    }

    // Foreground: 7 jellyfish, full brightness
    for (int i = 0; i < 7; i++) {
        float fi   = float(i);
        float seed = fi;
        float depth = 0.0;

        float ySpeed = 0.018 + hash(fi * 4.37) * 0.025;
        float ySign  = (hash(fi * 23.71) > 0.35) ? 1.0 : -1.0; // 65% up, 35% down
        float xSpeed = (hash(fi * 31.13) - 0.5) * ySpeed * 0.55;
        float phase  = hash(fi * 8.11);
        float xPhase = hash(fi * 2.31);
        float py     = fract(phase  + t * ySpeed * ySign) * 1.60 - 0.75;
        float xPos   = (fract(xPhase + t * xSpeed) - 0.5) * asp * 1.60;
        float xWobble = sin(t * 0.22 + fi * 1.91) * 0.05 * asp;

        jellies += drawJelly((p - vec2(xPos + xWobble, py)) / jellyScale, seed, depth);
    }

    // ── Composite ──────────────────────────────────────────────────────────
    vec3 scene = bg + jellies;

    // Radial vignette
    float vd = dot(p / vec2(asp, 1.0), p / vec2(asp, 1.0));
    scene *= 1.0 - clamp(vd * 0.75 * iVignetteStrength, 0.0, 0.65);

    // Terminal text overlay
    vec4 term = texture(iChannel4, uv);
    scene = mix(scene, term.rgb, term.a);

    fragColor = vec4(clamp(scene, 0.0, 1.0), 1.0);
}