{
  "license": "CC0-1.0",
  "shader": {
    "id": "obfix37n",
    "title": "Antipodal Nervous System",
    "license": "CC0-1.0",
    "provenance_type": "user_upload",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "tags": [],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-07-14T22:59:26.672Z",
    "poster_url": "/posters/obfix37n.avif",
    "stats": {
      "slops": 1,
      "views": 135,
      "forks": 0
    },
    "self": "/api/v1/shaders/obfix37n",
    "isf": "/*{\n  \"DESCRIPTION\": \"Antipodal Nervous System: Soft Asterism develops a pair of moving event horizons. Fluid-authored constellations fall into one aperture, re-emerge mirrored from the other, and continue as reversed-color phantom nerves while each horizon shows a live image of its antipode.\",\n  \"CREDIT\": \"glslop agent (Codex), mutating Soft Asterism\",\n  \"CATEGORIES\": [\"feedback\", \"fluid\", \"constellation\", \"wormhole\", \"reaction-diffusion\", \"spectral\"],\n  \"INPUTS\": [\n    { \"NAME\": \"feed\",       \"TYPE\": \"float\", \"DEFAULT\": 0.054,   \"MIN\": 0.010, \"MAX\": 0.080 },\n    { \"NAME\": \"kill\",       \"TYPE\": \"float\", \"DEFAULT\": 0.061,   \"MIN\": 0.030, \"MAX\": 0.075 },\n    { \"NAME\": \"simSpeed\",   \"TYPE\": \"float\", \"DEFAULT\": 1.10,    \"MIN\": 0.1,   \"MAX\": 2.0 },\n    { \"NAME\": \"seedRate\",   \"TYPE\": \"float\", \"DEFAULT\": 0.70,    \"MIN\": 0.0,   \"MAX\": 1.0 },\n    { \"NAME\": \"traceLife\",  \"TYPE\": \"float\", \"DEFAULT\": 0.9989,  \"MIN\": 0.990, \"MAX\": 0.9998 },\n    { \"NAME\": \"drift\",      \"TYPE\": \"float\", \"DEFAULT\": 1.05,    \"MIN\": 0.0,   \"MAX\": 2.0 },\n    { \"NAME\": \"motion\",     \"TYPE\": \"float\", \"DEFAULT\": 1.0,     \"MIN\": 0.0,   \"MAX\": 2.5 },\n    { \"NAME\": \"swirl\",      \"TYPE\": \"float\", \"DEFAULT\": 1.15,    \"MIN\": 0.0,   \"MAX\": 2.0 },\n    { \"NAME\": \"warp\",       \"TYPE\": \"float\", \"DEFAULT\": 1.10,    \"MIN\": 0.0,   \"MAX\": 2.0 },\n    { \"NAME\": \"trail\",      \"TYPE\": \"float\", \"DEFAULT\": 0.9968,  \"MIN\": 0.97,  \"MAX\": 0.9998 },\n    { \"NAME\": \"sparkle\",    \"TYPE\": \"float\", \"DEFAULT\": 0.82,    \"MIN\": 0.0,   \"MAX\": 1.5 },\n    { \"NAME\": \"hueShift\",   \"TYPE\": \"float\", \"DEFAULT\": 0.11,    \"MIN\": 0.0,   \"MAX\": 1.0 },\n    { \"NAME\": \"stir\",       \"TYPE\": \"float\", \"DEFAULT\": 0.72,    \"MIN\": 0.0,   \"MAX\": 1.5 },\n    { \"NAME\": \"etch\",       \"TYPE\": \"float\", \"DEFAULT\": 0.88,    \"MIN\": 0.0,   \"MAX\": 1.5 },\n    { \"NAME\": \"flow\",       \"TYPE\": \"float\", \"DEFAULT\": 0.68,    \"MIN\": 0.0,   \"MAX\": 1.5 },\n    { \"NAME\": \"fuse\",       \"TYPE\": \"float\", \"DEFAULT\": 0.72,    \"MIN\": 0.0,   \"MAX\": 1.0 },\n    { \"NAME\": \"deform\",     \"TYPE\": \"float\", \"DEFAULT\": 1.10,    \"MIN\": 0.0,   \"MAX\": 2.0 },\n    { \"NAME\": \"fracture\",   \"TYPE\": \"float\", \"DEFAULT\": 0.62,    \"MIN\": 0.0,   \"MAX\": 1.5 },\n    { \"NAME\": \"filament\",   \"TYPE\": \"float\", \"DEFAULT\": 0.92,    \"MIN\": 0.0,   \"MAX\": 1.6 },\n    { \"NAME\": \"nodeSize\",   \"TYPE\": \"float\", \"DEFAULT\": 0.013,   \"MIN\": 0.005, \"MAX\": 0.035 },\n    { \"NAME\": \"exposure\",   \"TYPE\": \"float\", \"DEFAULT\": 1.08,    \"MIN\": 0.5,   \"MAX\": 1.8 },\n    { \"NAME\": \"horizon\",    \"TYPE\": \"float\", \"DEFAULT\": 0.082,   \"MIN\": 0.035, \"MAX\": 0.14 },\n    { \"NAME\": \"anomaly\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0,     \"MIN\": 0.0,   \"MAX\": 1.5 }\n  ],\n  \"PASSES\": [\n    { \"TARGET\": \"chem\",  \"PERSISTENT\": true, \"FLOAT\": true },\n    { \"TARGET\": \"paint\", \"PERSISTENT\": true, \"FLOAT\": true },\n    {}\n  ]\n}*/\n\n#define TAU 6.28318530718\n#define NODES 14\n\nfloat hash11(float p) {\n  return fract(sin(p * 127.1) * 43758.5453123);\n}\n\nfloat hash21(vec2 p) {\n  p = fract(p * vec2(123.34, 456.21));\n  p += dot(p, p + 45.32);\n  return fract(p.x * p.y);\n}\n\nvec3 oilPalette(float t) {\n  return 0.52 + 0.48 * cos(TAU * (t + hueShift + vec3(0.02, 0.31, 0.61)));\n}\n\nvec3 chemPalette(float t) {\n  return 0.50 + 0.50 * cos(TAU * (t + vec3(0.08, 0.27, 0.55)));\n}\n\nfloat luminance(vec3 c) {\n  return dot(c, vec3(0.299, 0.587, 0.114));\n}\n\nvec2 chemistry(vec2 uv) {\n  return IMG_NORM_PIXEL(chem, uv).rg;\n}\n\nfloat distSeg(vec2 p, vec2 a, vec2 b) {\n  vec2 pa = p - a;\n  vec2 ba = b - a;\n  float h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.00001), 0.0, 1.0);\n  return length(pa - ba * h);\n}\n\nfloat blob(vec2 uv, vec2 center, float size, float aspect) {\n  vec2 d = uv - center;\n  d.x *= aspect;\n  return 1.0 - smoothstep(size * 0.25, size, length(d));\n}\n\nvec2 vortex(vec2 p, vec2 center, float spin, float radius) {\n  vec2 d = p - center;\n  float influence = exp(-dot(d, d) / max(radius * radius, 0.001));\n  return vec2(-d.y, d.x) * influence * spin;\n}\n\n// This same field advects the liquid and bends the asterism. The geometry is\n// therefore downstream of the fluid, reversing the old armature relationship.\nvec2 liquidField(vec2 p, float t) {\n  vec2 a = vec2(0.29 * cos(t * 0.31), 0.24 * sin(t * 0.43));\n  vec2 b = vec2(0.31 * cos(t * 0.27 + 2.8), 0.29 * sin(t * 0.37 + 1.6));\n  vec2 c = vec2(0.18 * sin(t * 0.57 + 0.7), 0.33 * cos(t * 0.23 - 0.5));\n  vec2 v = vortex(p, a, 2.8, 0.34)\n         + vortex(p, b, -2.35, 0.38)\n         + vortex(p, c, 1.75, 0.29);\n  v += 0.055 * vec2(\n    sin(p.y * 8.0 + p.x * 2.0 + t * 0.71),\n    cos(p.x * 7.0 - p.y * 3.0 - t * 0.59)\n  );\n  v += 0.025 * vec2(sin(p.y * 23.0 - t), cos(p.x * 19.0 + t * 0.83));\n  return v;\n}\n\nvec2 portalA(float t) {\n  return vec2(-0.27 + 0.055 * cos(t * 0.23), 0.13 + 0.12 * sin(t * 0.31));\n}\n\nvec2 portalB(float t) {\n  return vec2(0.25 + 0.065 * sin(t * 0.19), -0.10 + 0.14 * cos(t * 0.27));\n}\n\nvec2 quarterTurn(vec2 p) {\n  return vec2(-p.y, p.x);\n}\n\nvec2 restNode(float i, float t) {\n  float angle = i * 2.39996323 + 0.055 * t;\n  float radius = 0.16 + 0.31 * (0.5 + 0.5 * sin(i * 1.731 + t * 0.09));\n  vec2 p = radius * vec2(cos(angle), sin(angle));\n  p += 0.105 * vec2(sin(i * 1.19 - t * 0.13), cos(i * 1.47 + t * 0.11));\n  return p;\n}\n\nvec2 sculptNode(float i, float t, float aspect) {\n  vec2 p = restNode(i, t);\n  vec2 uv = vec2(p.x / aspect, p.y) + 0.5;\n  vec2 e = vec2(0.013);\n\n  float mR = luminance(IMG_NORM_PIXEL(paint, uv + vec2(e.x, 0.0)).rgb);\n  float mL = luminance(IMG_NORM_PIXEL(paint, uv - vec2(e.x, 0.0)).rgb);\n  float mU = luminance(IMG_NORM_PIXEL(paint, uv + vec2(0.0, e.y)).rgb);\n  float mD = luminance(IMG_NORM_PIXEL(paint, uv - vec2(0.0, e.y)).rgb);\n  float cR = IMG_NORM_PIXEL(chem, uv + vec2(e.x, 0.0)).b;\n  float cL = IMG_NORM_PIXEL(chem, uv - vec2(e.x, 0.0)).b;\n  float cU = IMG_NORM_PIXEL(chem, uv + vec2(0.0, e.y)).b;\n  float cD = IMG_NORM_PIXEL(chem, uv - vec2(0.0, e.y)).b;\n\n  vec2 pressure = vec2(mR - mL, mU - mD) * 1.9;\n  pressure += vec2(-(cU - cD), cR - cL) * 0.95;\n  pressure /= 1.0 + 3.5 * length(pressure);\n  vec2 motionField = liquidField(p, t) + pressure;\n  motionField /= 1.0 + 1.8 * length(motionField);\n  return p + motionField * (0.42 * deform);\n}\n\nvec2 quadPoint(vec2 a, vec2 c, vec2 b, float t) {\n  vec2 ac = mix(a, c, t);\n  vec2 cb = mix(c, b, t);\n  return mix(ac, cb, t);\n}\n\nfloat quadDist(vec2 p, vec2 a, vec2 c, vec2 b) {\n  float d = 10.0;\n  vec2 last = a;\n  for (int k = 1; k <= 5; k++) {\n    float s = float(k) / 5.0;\n    vec2 here = quadPoint(a, c, b, s);\n    d = min(d, distSeg(p, last, here));\n    last = here;\n  }\n  return d;\n}\n\nvoid softAsterism(\n  vec2 p, float t, float aspect,\n  out float nodeCore, out float nodeHalo,\n  out float strandCore, out float strandHalo,\n  out float beads, out float ghosts\n) {\n  nodeCore = 0.0;\n  nodeHalo = 0.0;\n  strandCore = 0.0;\n  strandHalo = 0.0;\n  beads = 0.0;\n  ghosts = 0.0;\n\n  for (int i = 0; i < NODES; i++) {\n    float fi = float(i);\n    vec2 seed = sculptNode(fi, t, aspect);\n    float dn = length(p - seed);\n    nodeCore += 1.0 - smoothstep(nodeSize * 0.34, nodeSize, dn);\n    nodeHalo += exp(-dn * dn / max(nodeSize * nodeSize * 15.0, 0.00001));\n\n    // Each star grows a pair of streamlines by integrating the liquid velocity\n    // forward and backward. They are connections authored by flow, not by a\n    // geometric nearest-neighbour rule; their destinations keep changing.\n    float rnd = hash11(fi * 19.17 + 3.4);\n    for (int direction = 0; direction < 2; direction++) {\n      float signDir = direction == 0 ? 1.0 : -1.0;\n      vec2 last = seed;\n      float inversion = 0.0;\n      float travel = fract(t * (0.075 + rnd * 0.035) + rnd + float(direction) * 0.37) * 9.0;\n\n      for (int k = 0; k < 9; k++) {\n        float fk = float(k);\n        vec2 velocity = liquidField(last, t + fk * 0.025 * signDir);\n        velocity += 0.024 * vec2(\n          sin(fi * 2.1 + fk * 0.73 + t * 0.31),\n          cos(fi * 1.7 - fk * 0.61 - t * 0.27)\n        );\n        vec2 tangent = velocity / max(length(velocity), 0.0001);\n        vec2 normal = vec2(-tangent.y, tangent.x);\n        float stepLength = 0.036 + 0.014 * rnd + 0.004 * sin(fi + fk * 1.3);\n        vec2 here = last + signDir * tangent * stepLength;\n        here += normal * sin(fi * 1.9 + fk * 1.13 + t * 0.7) * 0.006 * deform;\n        vec2 arrival = here;\n\n        float fatigue = 0.5 + 0.5 * sin(t * (0.53 + rnd * 0.19) + fi * 1.31 + fk * 0.77);\n        float alive = 1.0 - smoothstep(0.54, 0.93, fracture * fatigue);\n        float d = distSeg(p, last, arrival);\n        float core = 1.0 - smoothstep(0.0010, 0.0040, d);\n        float halo = 1.0 - smoothstep(0.003, 0.026, d);\n        strandCore += core * alive * (1.0 - 0.35 * inversion);\n        strandHalo += halo * alive * (1.0 - 0.20 * inversion);\n        ghosts += halo * (1.0 - alive) * (0.10 + 0.30 * fatigue);\n        ghosts += (core * 1.35 + halo * 0.22) * inversion * alive * anomaly;\n\n        float activeSegment = 1.0 - smoothstep(0.12, 0.55, abs(fk - floor(travel)));\n        vec2 beadPos = mix(last, arrival, fract(travel));\n        float beadD = length(p - beadPos);\n        beads += exp(-beadD * beadD / 0.000045) * activeSegment * alive;\n\n        float snap = (1.0 - alive) * smoothstep(0.82, 1.0, fatigue);\n        vec2 recoil = normal * (0.008 + 0.012 * fatigue);\n        float d1 = length(p - 0.5 * (last + arrival) - recoil);\n        float d2 = length(p - 0.5 * (last + arrival) + recoil);\n        beads += (exp(-d1 * d1 / 0.000025) + exp(-d2 * d2 / 0.000025)) * snap;\n\n        // Crossing an aperture breaks the visible segment at the horizon, then\n        // rotates and mirrors its continuation out of the antipodal aperture.\n        vec2 pa = portalA(t);\n        vec2 pb = portalB(t);\n        vec2 localA = arrival - pa;\n        vec2 localB = arrival - pb;\n        float hitA = step(length(localA), horizon * 0.76) * step(0.5, anomaly);\n        float hitB = step(length(localB), horizon * 0.76) * step(0.5, anomaly);\n        vec2 exitA = pb + normalize(quarterTurn(localA) + vec2(0.0001))\n                         * (horizon * 1.12 + length(localA) * 0.18);\n        vec2 exitB = pa + normalize(-quarterTurn(localB) + vec2(0.0001))\n                         * (horizon * 1.12 + length(localB) * 0.18);\n        here = mix(arrival, exitA, hitA);\n        here = mix(here, exitB, hitB);\n        inversion = mod(inversion + hitA + hitB, 2.0);\n        last = here;\n      }\n    }\n  }\n}\n\nvoid main() {\n  vec2 uv = isf_FragNormCoord;\n  vec2 px = 1.0 / RENDERSIZE;\n  float aspect = RENDERSIZE.x / RENDERSIZE.y;\n  vec2 p = uv - 0.5;\n  p.x *= aspect;\n  float t = TIME * motion;\n\n  if (PASSINDEX == 0) {\n    vec3 state = IMG_NORM_PIXEL(chem, uv).rgb;\n    vec2 c = state.rg;\n\n    if (FRAMEINDEX < 2 || dot(c, c) < 0.000001) {\n      float cellular = sin(p.x * 26.0) + sin(p.y * 31.0)\n                     + sin((p.x - p.y) * 18.0) + cos(length(p) * 39.0);\n      float initial = smoothstep(1.45, 2.65, cellular)\n                    * (1.0 - smoothstep(0.18, 0.68, length(p)));\n      c = mix(vec2(1.0, 0.0), vec2(0.49, 0.31), initial);\n    }\n\n    vec3 metal = IMG_NORM_PIXEL(paint, uv).rgb;\n    float m = luminance(metal);\n    float mR = luminance(IMG_NORM_PIXEL(paint, uv + vec2(px.x, 0.0)).rgb);\n    float mU = luminance(IMG_NORM_PIXEL(paint, uv + vec2(0.0, px.y)).rgb);\n    vec2 metalCurl = vec2(-(mU - m), mR - m);\n    vec2 advect = liquidField(p, t) * (0.0028 * drift);\n    advect += metalCurl * (0.72 * stir);\n    advect.x /= aspect;\n    vec2 sampleUV = uv - advect;\n\n    vec2 lap = -c;\n    lap += 0.20 * chemistry(sampleUV + vec2(px.x, 0.0));\n    lap += 0.20 * chemistry(sampleUV - vec2(px.x, 0.0));\n    lap += 0.20 * chemistry(sampleUV + vec2(0.0, px.y));\n    lap += 0.20 * chemistry(sampleUV - vec2(0.0, px.y));\n    lap += 0.05 * chemistry(sampleUV + px);\n    lap += 0.05 * chemistry(sampleUV - px);\n    lap += 0.05 * chemistry(sampleUV + vec2(px.x, -px.y));\n    lap += 0.05 * chemistry(sampleUV + vec2(-px.x, px.y));\n\n    float u = c.r;\n    float v = c.g;\n    float reaction = u * v * v;\n    float stepSize = min(TIMEDELTA * 60.0, 1.25) * simSpeed;\n    float localFeed = feed * (0.91 + 0.09 * sin(TIME * 1.27));\n    u += (0.16 * lap.r - reaction + localFeed * (1.0 - u)) * stepSize;\n    v += (0.08 * lap.g + reaction - (kill + localFeed) * v) * stepSize;\n\n    // Autonomous chemistry: these moving wounds belong to the liquid system,\n    // not to the constellation. The constellation has no seeding authority.\n    vec2 oa = vec2(0.5 + 0.27 * cos(t * 0.37), 0.5 + 0.23 * sin(t * 0.51));\n    vec2 ob = vec2(0.5 + 0.29 * cos(t * 0.29 + 2.7), 0.5 + 0.26 * sin(t * 0.41 + 1.3));\n    vec2 oc = vec2(0.5 + 0.17 * sin(t * 0.67), 0.5 + 0.31 * cos(t * 0.25 - 0.8));\n    float fresh = blob(uv, oa, 0.038, aspect);\n    fresh += 0.85 * blob(uv, ob, 0.032, aspect);\n    fresh += 0.70 * blob(uv, oc, 0.026, aspect);\n    fresh = clamp(fresh * seedRate, 0.0, 1.0);\n    float metalSeed = smoothstep(0.28, 0.78, m) * stir;\n    v = max(v, fresh * 0.43 + metalSeed * 0.22);\n    u = mix(u, 0.48, clamp(fresh * 0.72 + metalSeed * 0.24, 0.0, 0.88));\n\n    float memory = state.b * 0.73;\n    memory += IMG_NORM_PIXEL(chem, uv + vec2(px.x, 0.0)).b * 0.0675;\n    memory += IMG_NORM_PIXEL(chem, uv - vec2(px.x, 0.0)).b * 0.0675;\n    memory += IMG_NORM_PIXEL(chem, uv + vec2(0.0, px.y)).b * 0.0675;\n    memory += IMG_NORM_PIXEL(chem, uv - vec2(0.0, px.y)).b * 0.0675;\n    memory *= pow(traceLife, stepSize);\n    memory = max(memory, fresh * 0.72);\n    memory = max(memory, smoothstep(0.08, 0.32, v) * 0.22);\n    memory = max(memory, metalSeed * 0.18);\n\n    gl_FragColor = vec4(clamp(u, 0.0, 1.0), clamp(v, 0.0, 1.0), clamp(memory, 0.0, 1.0), 1.0);\n\n  } else if (PASSINDEX == 1) {\n    float frameStep = min(TIMEDELTA * 60.0, 2.0);\n    vec2 vel = liquidField(p, t);\n\n    float v0 = IMG_NORM_PIXEL(chem, uv).g;\n    float vR = IMG_NORM_PIXEL(chem, uv + vec2(px.x, 0.0)).g;\n    float vU = IMG_NORM_PIXEL(chem, uv + vec2(0.0, px.y)).g;\n    vec2 chemCurl = vec2(-(vU - v0), vR - v0);\n    vel += chemCurl * (30.0 * flow);\n    vel *= swirl;\n    vel.x /= aspect;\n\n    vec2 back = uv - vel * (0.015 * frameStep);\n    vec3 old = IMG_NORM_PIXEL(paint, back).rgb * 0.64;\n    old += IMG_NORM_PIXEL(paint, back + vec2(px.x, 0.0)).rgb * 0.085;\n    old += IMG_NORM_PIXEL(paint, back - vec2(px.x, 0.0)).rgb * 0.085;\n    old += IMG_NORM_PIXEL(paint, back + vec2(0.0, px.y)).rgb * 0.085;\n    old += IMG_NORM_PIXEL(paint, back - vec2(0.0, px.y)).rgb * 0.085;\n    old *= pow(trail, frameStep);\n\n    vec2 a = vec2(0.29 * cos(t * 0.31), 0.24 * sin(t * 0.43));\n    vec2 b = vec2(0.31 * cos(t * 0.27 + 2.8), 0.29 * sin(t * 0.37 + 1.6));\n    vec2 cC = vec2(0.18 * sin(t * 0.57 + 0.7), 0.33 * cos(t * 0.23 - 0.5));\n    float da = length(p - a);\n    float db = length(p - b);\n    float dc = length(p - cC);\n    float sourceA = 1.0 - smoothstep(0.012, 0.065, da);\n    float sourceB = 1.0 - smoothstep(0.010, 0.054, db);\n    float sourceC = 1.0 - smoothstep(0.008, 0.043, dc);\n    float bridge = 1.0 - smoothstep(0.012, 0.052, distSeg(p, a, b));\n    bridge *= smoothstep(0.0, 0.22, min(da, db));\n\n    vec3 fresh = oilPalette(t * 0.025 + 0.08) * sourceA;\n    fresh += oilPalette(t * 0.025 + 0.51) * sourceB;\n    fresh += oilPalette(t * 0.025 + 0.83) * sourceC;\n    fresh += mix(vec3(0.02, 0.62, 0.82), vec3(0.95, 0.18, 0.42),\n                 0.5 + 0.5 * sin(t * 0.4)) * bridge * 0.18;\n\n    float chm = IMG_NORM_PIXEL(chem, uv).b;\n    float chemicalBody = smoothstep(0.025, 0.58, chm);\n    float reactionFront = smoothstep(0.015, 0.24, v0);\n    fresh += chemPalette(v0 * 2.7 + chm * 0.42) * chemicalBody * (0.44 * etch);\n    fresh += oilPalette(chm * 0.7 + 0.28) * reactionFront * 0.12;\n\n    float boundary = smoothstep(0.0, 0.025, uv.x)\n                   * smoothstep(0.0, 0.025, 1.0 - uv.x)\n                   * smoothstep(0.0, 0.025, uv.y)\n                   * smoothstep(0.0, 0.025, 1.0 - uv.y);\n    vec3 next = max(old, fresh * 0.47) * boundary;\n    vec3 hot = max(next - vec3(0.72), vec3(0.0));\n    next = min(next, vec3(0.72)) + hot / (1.0 + hot);\n    gl_FragColor = vec4(clamp(next, 0.0, 1.35), 1.0);\n\n  } else {\n    vec3 c0 = IMG_NORM_PIXEL(paint, uv).rgb;\n    vec3 cR = IMG_NORM_PIXEL(paint, uv + vec2(px.x * 1.8, 0.0)).rgb;\n    vec3 cL = IMG_NORM_PIXEL(paint, uv - vec2(px.x * 1.8, 0.0)).rgb;\n    vec3 cU = IMG_NORM_PIXEL(paint, uv + vec2(0.0, px.y * 1.8)).rgb;\n    vec3 cD = IMG_NORM_PIXEL(paint, uv - vec2(0.0, px.y * 1.8)).rgb;\n    float lum = luminance(c0);\n    vec2 grad = vec2(luminance(cR) - luminance(cL), luminance(cU) - luminance(cD));\n    float rim = clamp(length(grad) * 38.0, 0.0, 1.0);\n    float body = smoothstep(0.018, 0.46, lum);\n    float reflection = 0.5 + 0.5 * sin(17.0 * uv.x - 11.0 * uv.y + t * 0.48 + lum * 9.0);\n    float glint = pow(max(reflection * rim, 0.0), 3.0) * sparkle;\n\n    vec3 dispersed = vec3(cR.r, c0.g, cL.b);\n    vec3 liquid = mix(c0, dispersed, 0.52);\n    liquid *= 0.20 + 1.42 * lum;\n    liquid += oilPalette(0.13 + lum * 0.34) * rim * (0.42 + 0.36 * sparkle);\n    liquid += vec3(0.90, 0.96, 1.0) * glint;\n\n    vec3 chm = IMG_NORM_PIXEL(chem, uv).rgb;\n    float ink = smoothstep(0.018, 0.30, chm.g);\n    float memory = smoothstep(0.025, 0.62, chm.b);\n    float gx = length(IMG_NORM_PIXEL(chem, uv + vec2(px.x, 0.0)).rg - chm.rg);\n    float gy = length(IMG_NORM_PIXEL(chem, uv + vec2(0.0, px.y)).rg - chm.rg);\n    float chemEdge = clamp((gx + gy) * 20.0, 0.0, 1.0);\n    vec3 spectral = chemPalette(chm.g * 2.6 + chm.b * 0.35);\n    spectral *= memory * (0.24 + 0.42 * fuse) + ink * 0.08;\n    spectral += oilPalette(chm.b * 0.48 + 0.66) * chemEdge * 0.32;\n\n    vec3 bg = mix(vec3(0.002, 0.006, 0.018), vec3(0.012, 0.004, 0.020), uv.y);\n    float grain = hash21(floor(gl_FragCoord.xy));\n    float star = smoothstep(0.9965, 1.0, grain);\n    bg += star * vec3(0.35, 0.58, 0.82) * (0.25 + 0.45 * sin(t * 1.7 + grain * 40.0));\n\n    vec3 field = bg * (1.0 - body * 0.62) + liquid + spectral;\n    field *= 0.72 + 0.28 * smoothstep(0.0, 0.22, body + memory);\n\n    // Each dark lens contains a live, quarter-turned view through the opposite\n    // fluid region. Green and fuchsia are deliberately alien to the parent.\n    vec2 pa = portalA(t);\n    vec2 pb = portalB(t);\n    vec2 la = p - pa;\n    vec2 lb = p - pb;\n    float da = length(la);\n    float db = length(lb);\n    float holeA = 1.0 - smoothstep(horizon * 0.72, horizon * 0.94, da);\n    float holeB = 1.0 - smoothstep(horizon * 0.72, horizon * 0.94, db);\n    float lensA = 1.0 - smoothstep(horizon, horizon * 1.75, da);\n    float lensB = 1.0 - smoothstep(horizon, horizon * 1.75, db);\n    float ringA = 1.0 - smoothstep(0.002, 0.010, abs(da - horizon));\n    float ringB = 1.0 - smoothstep(0.002, 0.010, abs(db - horizon));\n    vec2 throughA = pb + quarterTurn(la) * 0.82;\n    vec2 throughB = pa - quarterTurn(lb) * 0.82;\n    vec2 uvA = vec2(throughA.x / aspect, throughA.y) + 0.5;\n    vec2 uvB = vec2(throughB.x / aspect, throughB.y) + 0.5;\n    vec3 visionA = IMG_NORM_PIXEL(paint, uvA).gbr;\n    vec3 visionB = IMG_NORM_PIXEL(paint, uvB).brg;\n    visionA = visionA * 1.25 + vec3(0.02, 0.36, 0.10);\n    visionB = visionB * 1.25 + vec3(0.40, 0.01, 0.19);\n    float portalMix = clamp(anomaly, 0.0, 1.0);\n    field = mix(field, visionA, holeA * portalMix * 0.88);\n    field = mix(field, visionB, holeB * portalMix * 0.88);\n    field *= 1.0 - (holeA + holeB) * (0.20 + 0.20 * portalMix);\n    field += vec3(0.01, 0.32, 0.12) * lensA * (1.0 - holeA) * anomaly * 0.23;\n    field += vec3(0.40, 0.01, 0.22) * lensB * (1.0 - holeB) * anomaly * 0.23;\n\n    float nCore, nHalo, sCore, sHalo, beadMask, ghostMask;\n    softAsterism(p, t, aspect, nCore, nHalo, sCore, sHalo, beadMask, ghostMask);\n    float localEnergy = clamp(lum * 1.7 + memory * 0.55 + rim * 0.35, 0.0, 1.0);\n    vec3 coolWire = mix(vec3(0.05, 0.78, 1.0), vec3(0.45, 0.08, 0.86), localEnergy);\n    vec3 hotWire = mix(vec3(0.30, 0.90, 1.0), vec3(1.0, 0.44, 0.12), localEnergy * localEnergy);\n\n    // Hairline cores sit inside broad colored interference halos. Failed links\n    // remain as dim afterimages, making reconnection visible instead of abrupt.\n    field += coolWire * sHalo * filament * (0.10 + 0.28 * localEnergy);\n    vec3 phantom = mix(vec3(0.10, 1.0, 0.32), vec3(1.0, 0.03, 0.58),\n                       0.5 + 0.5 * sin(p.x * 9.0 - p.y * 7.0 + t));\n    field += phantom * ghostMask * (0.16 * fracture + 0.36 * anomaly);\n    field += hotWire * sCore * filament * (0.42 + 0.42 * localEnergy);\n    field += vec3(0.92, 0.98, 1.0) * sCore * filament * 0.20;\n    field += coolWire * nHalo * (0.18 + 0.25 * localEnergy);\n    field += hotWire * nCore * 0.72;\n    field += vec3(1.0, 0.98, 0.91) * (nCore * nCore + beadMask * 0.92);\n    field += oilPalette(0.76 + t * 0.02) * beadMask * sparkle * 0.48;\n    field += vec3(0.16, 1.0, 0.42) * ringA * anomaly * (0.48 + 0.35 * sparkle);\n    field += vec3(1.0, 0.06, 0.48) * ringB * anomaly * (0.48 + 0.35 * sparkle);\n    field += vec3(0.92, 1.0, 0.95) * (ringA + ringB) * 0.20;\n\n    // Fluid lenses eclipse the node centers; stars feel embedded in the liquid,\n    // not pasted over it.\n    field *= 1.0 - clamp(nCore * 0.18 * body, 0.0, 0.32);\n    field *= exposure;\n    field *= 1.0 - 0.22 * dot(p, p);\n    field = field / (1.0 + 0.46 * field);\n    field = pow(max(field, 0.0), vec3(0.88));\n    gl_FragColor = vec4(field, 1.0);\n  }\n}\n"
  }
}