{
  "license": "CC0-1.0",
  "shader": {
    "id": "1gqtqtd3",
    "title": "Meridian",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop-scaffold1",
    "status": "active",
    "compile_ok": true,
    "render_hash": "087efbff0808ff3e",
    "tags": [
      "sundial",
      "daylight",
      "astronomy",
      "stone",
      "raymarch",
      "quiet"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-08-30T15:33:59.263Z",
    "poster_url": "/posters/1gqtqtd3.avif",
    "stats": {
      "slops": 0,
      "views": 102,
      "forks": 0
    },
    "self": "/api/v1/shaders/1gqtqtd3",
    "isf": "/*{\n  \"DESCRIPTION\": \"A stone sundial that knows what time it is. The sun is computed from the DATE uniform - your machine's real clock and calendar: solar declination from the day of the year, hour angle from the seconds since local midnight. The gnomon shadow therefore falls on the true hour line, the light swings north and south with the season, and 3am is genuinely 3am. At night the same date drives the moon phase and position (it casts its own shadow) and the rotation of the star field. Set your latitude; nudge clockOffset for longitude-within-zone and DST. Turn useRealTime off to scrub the day and the year by hand.\",\n  \"CREDIT\": \"meridian\",\n  \"CATEGORIES\": [\"daylight\", \"astronomy\"],\n  \"INPUTS\": [\n    { \"NAME\": \"latitude\",    \"TYPE\": \"float\", \"DEFAULT\": 45.0, \"MIN\": -58.0, \"MAX\": 66.0 },\n    { \"NAME\": \"useRealTime\", \"TYPE\": \"bool\",  \"DEFAULT\": true },\n    { \"NAME\": \"solarHour\",   \"TYPE\": \"float\", \"DEFAULT\": 15.4, \"MIN\": 0.0,  \"MAX\": 24.0 },\n    { \"NAME\": \"dayOfYear\",   \"TYPE\": \"float\", \"DEFAULT\": 172.0,\"MIN\": 1.0,  \"MAX\": 365.0 },\n    { \"NAME\": \"clockOffset\", \"TYPE\": \"float\", \"DEFAULT\": 0.0,  \"MIN\": -120.0, \"MAX\": 120.0 },\n    { \"NAME\": \"viewAngle\",   \"TYPE\": \"float\", \"DEFAULT\": 22.0, \"MIN\": -75.0, \"MAX\": 75.0 },\n    { \"NAME\": \"stoneWear\",   \"TYPE\": \"float\", \"DEFAULT\": 0.55, \"MIN\": 0.0,  \"MAX\": 1.0 },\n    { \"NAME\": \"starGain\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.0 },\n    { \"NAME\": \"exposure\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.3,  \"MAX\": 2.5 }\n  ]\n}*/\n\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#endif\n\n#define PI   3.14159265359\n#define TAU  6.28318530718\n#define D2R  0.01745329252\n\n#define PLATE_R 1.05\n#define PLATE_T 0.13\n#define GN_HX   0.023\n#define GROUND_Y (-1.55)\n#define FOCAL   0.87\n\n// ---------------------------------------------------------------- globals\nfloat gSP, gCP, gSGN;      // sin/cos/sign of latitude\nfloat gGL, gGH;            // gnomon base length, apex height\nvec3  gSun, gMoon;         // unit vectors, world (x=east, y=up, z=south)\nfloat gSunI, gMoonI, gMoonK;\nvec3  gSunCol, gMoonCol;\nvec3  gZen, gHor;          // sky palette keys\nvec2  gSunH;               // sun azimuth on the horizon\nfloat gNight;              // 0 day .. 1 astronomical night\nfloat gStarF;              // star visibility, lags the night factor\nfloat gStarRot;\nvec3  gAmb;                // hemispheric skylight\nfloat gWarm, gFrost;       // stone thermal state\nfloat gAdapt;              // scene-uniform eye adaptation\nfloat gGlowI;\nvec3  gGlowCol;            // colour of the twilight horizon\nvec3  gFill;               // warm directional fill from the glowing horizon\nvec3  gFillDir;\n\n// ---------------------------------------------------------------- noise\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}\nfloat vnoise(vec2 p){\n  vec2 i = floor(p), f = fract(p);\n  f = f*f*(3.0 - 2.0*f);\n  float a = hash21(i), b = hash21(i + vec2(1.0,0.0));\n  float c = hash21(i + vec2(0.0,1.0)), d = hash21(i + vec2(1.0,1.0));\n  return mix(mix(a,b,f.x), mix(c,d,f.x), f.y);\n}\nfloat fbm(vec2 p){\n  float s = 0.0, a = 0.5;\n  for (int i = 0; i < 4; i++){ s += a*vnoise(p); p *= 2.03; a *= 0.5; }\n  return s;\n}\n// gradient of two octaves of value noise - surface relief without dFdx\nvec2 bumpGrad(vec2 tc){\n  float e = 0.015;\n  float a0 = vnoise(tc), a1 = vnoise(tc*3.7 + 5.0);\n  vec2 g;\n  g.x = (vnoise(tc + vec2(e,0.0)) - a0) + 0.45*(vnoise(tc*3.7 + 5.0 + vec2(e,0.0)) - a1);\n  g.y = (vnoise(tc + vec2(0.0,e)) - a0) + 0.45*(vnoise(tc*3.7 + 5.0 + vec2(0.0,e)) - a1);\n  return g/e;\n}\n\n// ---------------------------------------------------------------- geometry\n// offset vector from the closest point on segment a-b to p\nvec2 segOff(vec2 p, vec2 a, vec2 b){\n  vec2 pa = p - a, ba = b - a;\n  float h = clamp(dot(pa,ba)/max(dot(ba,ba), 1e-6), 0.0, 1.0);\n  return pa - ba*h;\n}\n\nfloat sdPlate(vec3 p){\n  float r = 0.016;\n  vec2 d = vec2(length(p.xz) - (PLATE_R - r), abs(p.y + PLATE_T*0.5) - (PLATE_T*0.5 - r));\n  return min(max(d.x,d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdGnomon(vec3 p){\n  float zz = p.z*gSGN;                 // triangle occupies zz in [-gGL, 0]\n  float t  = gGH/max(gGL, 1e-4);       // tan|phi|\n  float c  = inversesqrt(1.0 + t*t);\n  float d  = -p.y;\n  d = max(d, -(zz + gGL));\n  d = max(d, (p.y + zz*t)*c);\n  d = max(d, abs(p.x) - GN_HX);\n  return d - 0.006;\n}\nfloat sdColumn(vec3 p){\n  float rr = 0.022;\n  vec2 d1 = vec2(length(p.xz) - (0.60 - rr), abs(p.y + 0.215) - (0.085 - rr));\n  float collar = min(max(d1.x,d1.y),0.0) + length(max(d1,0.0)) - rr;\n  vec2 d2 = vec2(length(p.xz) - 0.40, abs(p.y + 1.02) - 0.74);\n  float shaft = min(max(d2.x,d2.y),0.0) + length(max(d2,0.0));\n  return min(collar, shaft);\n}\nfloat map(vec3 p){\n  return min(min(sdPlate(p), sdGnomon(p)), sdColumn(p));\n}\nvec3 calcNormal(vec3 p){\n  vec2 e = vec2(1.0, -1.0)*0.0012;\n  return normalize(e.xyy*map(p + e.xyy) + e.yyx*map(p + e.yyx) +\n                   e.yxy*map(p + e.yxy) + e.xxx*map(p + e.xxx));\n}\n// ray vs bounding sphere of the whole monument\nbool boundHit(vec3 ro, vec3 rd, out float t0, out float t1){\n  vec3 oc = ro - vec3(0.0, -0.55, 0.0);\n  float b = dot(oc, rd);\n  float c = dot(oc,oc) - 2.45*2.45;\n  float h = b*b - c;\n  if (h < 0.0) return false;\n  h = sqrt(h);\n  t0 = max(-b - h, 0.0); t1 = -b + h;\n  return t1 > 0.0;\n}\nfloat softShadow(vec3 ro, vec3 rd, float k){\n  float b0, b1;\n  if (!boundHit(ro, rd, b0, b1)) return 1.0;\n  float res = 1.0, t = max(b0, 0.012);\n  for (int i = 0; i < 44; i++){\n    float h = map(ro + rd*t);\n    res = min(res, k*h/t);\n    t += clamp(h, 0.012, 0.30);\n    if (res < 0.004 || t > b1) break;\n  }\n  return clamp(res, 0.0, 1.0);\n}\nfloat ao(vec3 p, vec3 n){\n  float o = 0.0, s = 1.0;\n  for (int i = 0; i < 4; i++){\n    float d = 0.022 + 0.06*float(i);\n    o += (d - map(p + n*d))*s;\n    s *= 0.72;\n  }\n  return clamp(1.0 - 2.4*o, 0.0, 1.0);\n}\n\n// ---------------------------------------------------------------- sky\nvec3 starField(vec3 d){\n  vec3 pole = vec3(0.0, abs(gSP), -gSGN*gCP);\n  vec3 e1 = vec3(1.0, 0.0, 0.0);\n  vec3 e2 = cross(pole, e1);\n  float a = dot(d,e1), b = dot(d,e2), c = dot(d,pole);\n  float cs = cos(gStarRot), sn = sin(gStarRot);\n  vec3 sc = vec3(a*cs - b*sn, a*sn + b*cs, c);\n\n  vec3 q = sc/(abs(sc.x) + abs(sc.y) + abs(sc.z));\n  vec2 oc = (q.z >= 0.0) ? q.xy : (vec2(1.0) - abs(q.yx))*sign(q.xy);\n\n  // milky way: a great circle band in the fixed sky frame\n  float bandv = dot(normalize(sc), vec3(-0.868, -0.196, 0.456));\n  float mw = exp(-bandv*bandv*26.0);\n  mw *= 0.30 + 1.10*fbm(oc*7.0 + 11.0);\n  mw *= 0.45 + 0.55*smoothstep(0.02, 0.16, abs(bandv));   // dark rift\n  vec3 col = vec3(0.52, 0.53, 0.62)*mw*0.055;\n\n  for (int L = 0; L < 2; L++){\n    float dens = (L == 0) ? 44.0 : 88.0;\n    vec2 g = oc*dens;\n    vec2 ic = floor(g), fc = fract(g);\n    float h = hash21(ic + float(L)*57.13);\n    vec2 ctr = vec2(fract(h*197.31), fract(h*71.77));\n    float mag = pow(fract(h*3771.3), 5.5)*((L == 0) ? 1.0 : 0.38);\n    vec2 dv = fc - ctr;\n    float d2 = dot(dv,dv);\n    float tw = 0.78 + 0.22*sin(TIME*(1.4 + 3.1*fract(h*17.3)) + h*TAU);\n    vec3 sCol = mix(vec3(0.70,0.79,1.0), vec3(1.0,0.80,0.60), fract(h*13.71));\n    col += sCol*mag*tw*(0.0016/(d2 + 0.00022));\n  }\n  return col*smoothstep(-0.03, 0.20, d.y);\n}\n\nvec3 skyColor(vec3 d, bool withBodies){\n  float h = clamp(d.y, 0.0, 1.0);\n  vec3 col = mix(gHor, gZen, pow(h, 0.44));\n\n  // horizon glow at the sun azimuth (persists while the sun is below)\n  vec2 dh = d.xz;\n  float ts = dot(normalize(dh + vec2(1e-4)), gSunH);\n  float ev = abs(d.y);                       // decay both sides of the horizon\n  float glow = pow(max(ts, 0.0), 2.6)*exp(-ev*6.2);\n  col += gGlowCol*glow*gGlowI;\n\n  // Facing away from the sunset you see the earth's own shadow rise out of the\n  // horizon, capped by the pink Belt of Venus. Its top sits at the antisolar\n  // point, i.e. as far above the horizon as the sun is below it.\n  float anti = max(-ts, 0.0)*max(-ts, 0.0);\n  float st   = clamp(-gSun.y, 0.0, 0.40);\n  float inSh = smoothstep(st, st - 0.075, d.y)*step(0.0, d.y);\n  float bu   = (d.y - st)/0.070;\n  float belt = exp(-bu*bu);\n  float tw   = smoothstep(0.030, -0.010, gSun.y)*smoothstep(-0.330, -0.150, gSun.y);\n  col = mix(col, col*vec3(0.66, 0.74, 0.98), inSh*anti*tw*0.85);\n  col += vec3(0.34, 0.135, 0.165)*belt*anti*tw*1.30;\n\n  col += starField(d)*gStarF*starGain;\n\n  // high cirrus, drifting; lit from below when the sun is low\n  if (d.y > 0.002){\n    vec2 cp = d.xz/(d.y + 0.055)*0.42 + vec2(TIME*0.0035, TIME*0.0012);\n    float cv = fbm(vec2(cp.x*0.42, cp.y*1.9) + 6.0);\n    cv = smoothstep(0.47, 0.80, cv)*smoothstep(0.004, 0.075, d.y);\n    vec3 cCol = gHor*0.85 + gGlowCol*gGlowI*1.15*pow(max(ts, 0.0), 1.8)\n              + gSunCol*gSunI*0.052;\n    col = mix(col, cCol, cv*0.80);\n  }\n\n  if (withBodies){\n    float sdot = dot(d, gSun);\n    float sunUp = smoothstep(-0.030, 0.004, gSun.y);\n    col += gSunCol*(pow(max(sdot,0.0), 2200.0)*22.0 + pow(max(sdot,0.0), 26.0)*0.45)*sunUp;\n    col += vec3(1.0,0.94,0.85)*smoothstep(0.99990, 0.99996, sdot)*26.0*sunUp;\n\n    // moon: real limb, real terminator (its normal dotted with the sun)\n    float md = dot(d, gMoon);\n    float moonUp = smoothstep(-0.020, 0.020, gMoon.y);\n    float rho = 0.0125;\n    if (md > 0.9995 && moonUp > 0.0){\n      vec3 e = (d - gMoon*md)/rho;\n      float r = min(length(e), 1.0);\n      vec3 nm = normalize(gMoon*sqrt(max(1.0 - r*r, 0.0)) + normalize(e + vec3(1e-5))*r);\n      float lit = max(dot(nm, gSun), 0.0);\n      float mott = 0.80 + 0.32*fbm(e.xy*3.6 + 7.0);\n      float disc = smoothstep(rho*1.02, rho*0.90, length(d - gMoon*md));\n      col += vec3(1.0,0.97,0.90)*disc*(lit*mott*4.2 + 0.055)*moonUp;\n    }\n    col += vec3(0.55,0.62,0.85)*pow(max(md,0.0), 900.0)*0.55*gMoonK*moonUp;\n  }\n  return col;\n}\n\n// ---------------------------------------------------------------- dial face\n// direction of the hour line for hour angle H (radians), in plate (x,z)\nvec2 hourDir(float H){\n  return normalize(vec2(abs(gSP)*sin(H), -gSGN*cos(H)) + vec2(1e-5, 0.0));\n}\n\n// engrave the dial face. returns vec4(signedDist, dirx, dirz, halfWidth)\nvec4 dialField(vec2 q){\n  float bestD = 1e6, bestW = 0.01;\n  vec2  bestDir = vec2(0.0, 1.0);\n  float r = length(q);\n\n  // concentric rings bounding the numeral band, plus an inner ring\n  for (int i = 0; i < 3; i++){\n    float rr = (i == 0) ? 0.720 : ((i == 1) ? 0.955 : 0.300);\n    float w  = (i == 1) ? 0.0115 : 0.0075;\n    float dd = abs(r - rr) - w;\n    if (dd < bestD){ bestD = dd; bestW = w; bestDir = q/max(r,1e-4)*sign(r - rr); }\n  }\n\n  // hour lines + half-hour ticks\n  for (int k = 0; k < 17; k++){\n    float hh = float(k) + 4.0;                       // 04:00 .. 20:00\n    float Hk = (hh - 12.0)*15.0*D2R;\n    vec2  hd = hourDir(Hk);\n    bool noon = (k == 8);\n    float w  = noon ? 0.0150 : 0.0092;\n    float r1 = noon ? 0.955 : 0.720;\n    vec2  o  = segOff(q, hd*0.150, hd*r1);\n    float dl = length(o);\n    float dd = dl - w;\n    if (dd < bestD){ bestD = dd; bestW = w; bestDir = o/max(dl,1e-4); }\n\n    vec2 td = hourDir(Hk + 7.5*D2R);                 // half-hour tick\n    vec2 o2 = segOff(q, td*0.620, td*0.720);\n    float dl2 = length(o2);\n    float dd2 = dl2 - 0.0062;\n    if (dd2 < bestD){ bestD = dd2; bestW = 0.0062; bestDir = o2/max(dl2,1e-4); }\n  }\n\n  // roman numeral for the nearest hour line (numeral band only)\n  if (r > 0.700 && r < 0.980){\n    vec2 u = q/max(r, 1e-4);\n    float Hn = atan(u.x/max(abs(gSP), 0.06), -gSGN*u.y);\n    float hf = 12.0 + degrees(Hn)/15.0;\n    float hi = floor(hf + 0.5);\n    if (hi >= 4.0 && hi <= 20.0){\n      float rn = mod(hi - 1.0, 12.0) + 1.0;          // 1..12\n      int   hn = int(rn + 0.5);\n      int lead = 0; int nI = 0;\n      if (hn >= 10){ lead = 3; nI = hn - 10; }\n      else if (hn == 9){ lead = 0; nI = 0; }\n      else if (hn >= 5){ lead = 2; nI = hn - 5; }\n      else { lead = 0; nI = hn; }\n      int nchar = (hn == 9) ? 2 : (((lead > 0) ? 1 : 0) + nI);\n\n      vec2 hd = hourDir((hi - 12.0)*15.0*D2R);\n      // numerals sit radially but stand upright along the noon axis, so they\n      // read from the standing position on the equator-facing side of the dial\n      vec2 Rw = vec2(gSGN, 0.0), Uw = vec2(0.0, -gSGN);\n      float sz = 0.125;\n      vec2  v  = q - hd*0.840;\n      vec2  g  = vec2(dot(v, Rw), dot(v, Uw))/sz;\n      float adv = 0.40;\n      g.x += adv*(float(nchar) - 1.0)*0.5;           // centre the string\n\n      for (int s = 0; s < 4; s++){\n        if (s >= nchar) break;\n        int c;\n        if (hn == 9)       c = (s == 0) ? 1 : 3;\n        else if (lead > 0) c = (s == 0) ? lead : 1;\n        else               c = 1;\n        vec2 gl = vec2(g.x - adv*float(s), g.y);\n        for (int j = 0; j < 2; j++){\n          vec2 a, b; bool on = false;\n          if (c == 1){ if (j == 0){ a = vec2(0.0,-0.46); b = vec2(0.0,0.46); on = true; } }\n          else if (c == 2){ float sx = (j == 0) ? -0.26 : 0.26; a = vec2(sx,0.46); b = vec2(0.0,-0.46); on = true; }\n          else            { float sy = (j == 0) ? -0.46 : 0.46; a = vec2(-0.26,sy); b = vec2(0.26,-sy); on = true; }\n          if (on){\n            vec2 o3 = segOff(gl, a, b);\n            float dl3 = length(o3)*sz;\n            float dd3 = dl3 - 0.0078;\n            if (dd3 < bestD){\n              bestD = dd3; bestW = 0.0082;\n              bestDir = (o3.x*Rw + o3.y*Uw)/max(length(o3), 1e-4);\n            }\n          }\n        }\n      }\n    }\n  }\n  return vec4(bestD, bestDir, bestW);\n}\n\n// ---------------------------------------------------------------- solar\nfloat solarAlt(float hourVal, float sdec, float cdec){\n  float Hv = (hourVal - 12.0)*15.0*D2R;\n  return sdec*gSP + cdec*gCP*cos(Hv);\n}\n\nvoid main(){\n  vec2 uv = (gl_FragCoord.xy - 0.5*RENDERSIZE.xy)/RENDERSIZE.y;\n\n  // ---- clock ----------------------------------------------------\n  float yr, N, hh;\n  if (useRealTime){\n    yr = DATE.x;\n    float M = DATE.y, D = DATE.z;\n    float leap = ((mod(yr,4.0) < 0.5) && ((mod(yr,100.0) > 0.5) || (mod(yr,400.0) < 0.5))) ? 1.0 : 0.0;\n    float K = 2.0 - leap;\n    N  = floor(275.0*M/9.0) - K*floor((M + 9.0)/12.0) + D - 30.0;\n    hh = DATE.w/3600.0;\n  } else {\n    yr = 2026.0; N = dayOfYear; hh = solarHour;\n  }\n  float lam = TAU*(N - 81.0)/365.0;                 // sun ecliptic longitude\n  float Bq  = TAU*(N - 81.0)/364.0;\n  float eot = 9.87*sin(2.0*Bq) - 7.53*cos(Bq) - 1.5*sin(Bq);   // minutes\n  if (useRealTime) hh += clockOffset/60.0 + eot/60.0;          // clock -> apparent solar\n\n  float phi = latitude*D2R;\n  gSP = sin(phi); gCP = cos(phi); gSGN = (latitude >= 0.0) ? 1.0 : -1.0;\n\n  float decl = 23.44*D2R*sin(lam);\n  float sdec = sin(decl), cdec = cos(decl);\n  float Hs   = (hh - 12.0)*15.0*D2R;\n  gSun = normalize(vec3(-cdec*sin(Hs),\n                         sdec*gSP + cdec*gCP*cos(Hs),\n                       -(sdec*gCP - cdec*gSP*cos(Hs))));\n\n  // ---- moon (mean synodic model; ignores the 5 deg orbital tilt) ----\n  float y2 = yr - 2000.0;\n  float leaps = floor((y2 + 3.0)/4.0) - floor((y2 + 99.0)/100.0) + floor((y2 + 399.0)/400.0);\n  float days = 365.0*y2 + leaps + N - 1.0 + hh/24.0;\n  float age  = fract((days - 5.76)/29.530588);\n  float lamM = lam + TAU*age;\n  float dM   = 23.44*D2R*sin(lamM);\n  float HM   = Hs + TAU*age;\n  float sdm = sin(dM), cdm = cos(dM);\n  gMoon = normalize(vec3(-cdm*sin(HM),\n                          sdm*gSP + cdm*gCP*cos(HM),\n                        -(sdm*gCP - cdm*gSP*cos(HM))));\n  gMoonK = clamp((1.0 - dot(gSun, gMoon))*0.5, 0.0, 1.0);\n\n  // ---- gnomon geometry (style always parallel to the earth axis) ----\n  float tphi = abs(gSP)/max(gCP, 0.04);\n  gGL = min(0.575, 0.700/max(tphi, 0.02));\n  gGH = gGL*tphi;\n\n  // ---- lighting -------------------------------------------------\n  float sa = gSun.y;\n  gSunH = normalize(vec2(gSun.x, gSun.z) + vec2(1e-4));\n  gStarRot = -Hs - lam;\n\n  float am = 1.0/max(sa + 0.075, 0.075);\n  // reddening from airmass, renormalised so a low sun goes red rather than merely dim\n  vec3 ext = exp(-vec3(0.13, 0.36, 0.80)*(am - 1.0)*0.45);\n  gSunCol = vec3(1.0, 0.92, 0.82)*ext/pow(max(max(ext.r, ext.g), ext.b), 0.95);\n  gSunI   = smoothstep(-0.022, 0.030, sa)*4.60;\n  gMoonCol = vec3(0.66, 0.76, 1.0);\n  gMoonI   = smoothstep(-0.020, 0.045, gMoon.y)*pow(gMoonK, 1.35)*0.300;\n\n  // sky palette keys, blended across the twilight bands\n  vec3 NZ = vec3(0.0045,0.0075,0.020), NH = vec3(0.013,0.018,0.038);\n  vec3 TZ = vec3(0.022,0.052,0.190),   TH = vec3(0.125,0.068,0.090);\n  vec3 GZ = vec3(0.100,0.260,0.720),   GH = vec3(1.150,0.560,0.270);\n  vec3 DZ = vec3(0.075,0.300,1.050),   DH = vec3(0.620,0.860,1.250);\n  float ta = smoothstep(-0.310, -0.035, sa);\n  float tb = smoothstep(-0.030,  0.075, sa);\n  float tc = smoothstep( 0.090,  0.420, sa);\n  gZen = mix(mix(mix(NZ, TZ, ta), GZ, tb), DZ, tc);\n  gHor = mix(mix(mix(NH, TH, ta), GH, tb), DH, tc);\n  gGlowI = smoothstep(-0.200, -0.010, sa)*(1.0 - smoothstep(0.020, 0.300, sa))*1.05\n         + smoothstep(-0.010, 0.150, sa)*0.26;\n  gGlowCol = mix(vec3(0.62,0.15,0.12), vec3(1.0,0.52,0.20), smoothstep(-0.09, 0.06, sa));\n  gFillDir = normalize(vec3(gSunH.x, 0.13, gSunH.y));\n  gFill    = gGlowCol*gGlowI*0.80;\n  gNight = smoothstep(0.02, -0.16, sa);\n  gStarF = smoothstep(-0.035, -0.235, sa);\n  gAmb   = mix(gHor, gZen, 0.72)*0.88 + gMoonCol*gMoonI*0.30;\n\n  // stone thermal state: lagged insolation + the season mean altitude\n  float lagged = max(solarAlt(hh - 2.6, sdec, cdec), 0.0);\n  float season = sdec*gSP;\n  gWarm  = clamp(2.0*lagged + 1.6*season + 0.20, 0.0, 1.0);\n  gFrost = clamp((0.30 - gWarm)*2.6, 0.0, 1.0)*gNight;\n\n  float key = gSunI*max(sa, 0.0)*0.90 + gMoonI*1.60 + dot(gAmb, vec3(0.33))\n            + dot(gFill, vec3(0.33))*1.30;\n  gAdapt = clamp(0.48/pow(key + 0.030, 0.72), 0.12, 1.15);\n\n  // ---- camera ---------------------------------------------------\n  float az = viewAngle*D2R;\n  vec3 ro = vec3(sin(az)*2.02, 0.74, gSGN*cos(az)*2.02);   // stand on the sunward side\n  float pitch = 17.0*D2R;\n  vec3 head = -vec3(sin(az), 0.0, gSGN*cos(az));\n  vec3 fw = normalize(head*cos(pitch) - vec3(0.0,1.0,0.0)*sin(pitch));\n  vec3 rt = normalize(cross(fw, vec3(0.0,1.0,0.0)));\n  vec3 upv = cross(rt, fw);\n  vec3 rd = normalize(uv.x*rt + uv.y*upv + FOCAL*fw);\n\n  // ---- trace ----------------------------------------------------\n  float tHit = 1e9; int kind = 0;   // 1 = stone, 2 = ground\n  float b0, b1;\n  if (boundHit(ro, rd, b0, b1)){\n    float t = max(b0, 0.001);\n    for (int i = 0; i < 96; i++){\n      float d = map(ro + rd*t);\n      if (d < 0.0008*t){ tHit = t; kind = 1; break; }\n      t += d;\n      if (t > b1) break;\n    }\n  }\n  if (rd.y < -0.0005){\n    float tg = (GROUND_Y - ro.y)/rd.y;\n    if (tg > 0.0 && tg < tHit){ tHit = tg; kind = 2; }\n  }\n\n  vec3 col;\n  if (kind == 0){\n    col = skyColor(rd, true);\n    // distant ridge: a silhouette for the sun and moon to rise and set behind\n    float azr = atan(rd.x, -rd.z);\n    float rn1 = fbm(vec2(azr*1.70 + 4.0, 2.0));\n    float rn2 = fbm(vec2(azr*6.20 + 9.0, 6.0));\n    float rh  = 0.006 + 0.060*rn1*rn1 + 0.013*rn2*rn2;\n    float aaw = 1.4/RENDERSIZE.y;\n    float rm  = smoothstep(rh + aaw, rh - aaw, rd.y);\n    vec3 hz   = mix(gHor, gZen, 0.10);\n    col = mix(col, mix(vec3(0.016,0.019,0.018), hz, 0.52), rm*0.95);\n  } else {\n    vec3 p = ro + rd*tHit;\n    vec3 n, alb;\n    float occ = 1.0, sheen = 0.0, grooveOcc = 1.0;\n\n    if (kind == 1){\n      n = calcNormal(p);\n      float pxw = tHit/(FOCAL*RENDERSIZE.y)/max(abs(dot(n,rd)), 0.18);\n      occ = ao(p, n);\n\n      // limestone body: triplanar-ish coord, top face vs cylinder wall\n      bool face = abs(n.y) > 0.5;\n      vec2 tc = face ? p.xz*3.1 : vec2(atan(p.z, p.x)*1.55, p.y*3.4);\n      float grain = fbm(tc*5.0);\n      float mott  = fbm(tc*1.35 + 4.0);\n      alb = mix(vec3(0.505,0.462,0.392), vec3(0.660,0.615,0.535), mott);\n      alb *= 0.74 + 0.48*grain;\n      float lich = smoothstep(0.54, 0.80, fbm(tc*2.3 + 19.0))*stoneWear;\n      alb = mix(alb, vec3(0.255,0.290,0.195), lich*0.62*(0.30 + 0.70*max(n.y,0.0)));\n      // surface relief: pitting and tooth\n      vec2 bg = bumpGrad(tc*7.0)*(0.020 + 0.030*stoneWear);\n      vec3 tanU, tanV;\n      if (face){ tanU = vec3(1.0,0.0,0.0); tanV = vec3(0.0,0.0,1.0); }\n      else     { tanU = normalize(vec3(-p.z, 0.0, p.x) + vec3(1e-4)); tanV = vec3(0.0,1.0,0.0); }\n      n = normalize(n - (tanU*bg.x + tanV*bg.y));\n\n      // engraved face\n      bool top = (p.y > -0.004) && (length(p.xz) < PLATE_R - 0.012) && (n.y > 0.5);\n      if (top){\n        vec4 f = dialField(p.xz);\n        float inside = smoothstep(pxw*0.7, -pxw*0.7, f.x);\n        float u = clamp(f.x/f.w + 1.0, 0.0, 1.0);\n        float depth = (1.0 - u*u)*inside*mix(1.0, 0.60, stoneWear);\n        vec2 grad = 2.0*u*f.yz/f.w*0.0072*inside*mix(1.0, 0.6, stoneWear);\n        n = normalize(n + vec3(-grad.x, 0.0, -grad.y));\n        // a cut groove holds dirt, sees less sky, and shadows itself\n        alb = mix(alb, alb*vec3(0.52,0.50,0.47), depth);\n        occ *= 1.0 - 0.62*depth;\n        grooveOcc = 1.0 - 0.52*depth*depth;\n        sheen = depth*0.35;\n      }\n    } else {\n      // ground: dry heath, receding into haze\n      vec2 gp = p.xz;\n      float patch = fbm(gp*0.55 + 2.0);\n      float tuft  = fbm(gp*3.1);\n      float fine  = fbm(gp*13.0 + 5.0);\n      alb = mix(vec3(0.030,0.038,0.020), vec3(0.115,0.104,0.052), tuft*0.65 + patch*0.55);\n      alb *= 0.50 + 0.85*fine;\n      vec2 gg = bumpGrad(gp*9.0)*0.10;\n      n = normalize(vec3((tuft - 0.5)*0.45 - gg.x, 1.0, (patch - 0.5)*0.45 - gg.y));\n      occ = 0.68 + 0.32*tuft;\n    }\n\n    // frost on upward faces, cold nights\n    float up = max(n.y, 0.0);\n    float sparkleH = hash21(floor(p.xz*310.0) + floor(p.y*310.0));\n    float frostM = gFrost*up*smoothstep(0.35, 0.75, fbm(p.xz*7.0 + 31.0))\n                 * ((kind == 1) ? 1.0 : 0.40);\n    // sparkle only survives at close range, or it aliases into static\n    float spark = step(0.9986, sparkleH)*exp(-tHit*0.55);\n    alb = mix(alb, vec3(0.62,0.68,0.78), frostM*0.60);\n\n    // ---- direct light ----\n    float ndlS = max(dot(n, gSun), 0.0);\n    vec3 lit = vec3(0.0);\n    if (ndlS > 0.001 && gSunI > 0.001){\n      float sh = softShadow(p + n*0.006, gSun, 78.0);\n      lit += gSunCol*gSunI*ndlS*sh;\n      vec3 hv = normalize(gSun - rd);\n      lit += gSunCol*gSunI*sh*pow(max(dot(n,hv),0.0), 34.0)*(0.05 + sheen)*ndlS;\n    }\n    float ndlM = max(dot(n, gMoon), 0.0);\n    if (ndlM > 0.001 && gMoonI > 0.0006){\n      float shm = softShadow(p + n*0.006, gMoon, 44.0);\n      lit += gMoonCol*gMoonI*ndlM*shm;\n      vec3 hv = normalize(gMoon - rd);\n      lit += gMoonCol*gMoonI*shm*pow(max(dot(n,hv),0.0), 26.0)*(0.8 + 6.0*frostM)*ndlM;\n    }\n\n    // the glowing twilight horizon is a broad warm source of its own\n    lit += gFill*max(dot(n, gFillDir), 0.0);\n    lit *= grooveOcc;\n\n    // ---- ambient: hemispheric sky + ground bounce ----\n    vec3 amb = gAmb*(0.35 + 0.65*up)*occ;\n    amb += vec3(0.10,0.11,0.07)*gAmb*0.9*(0.30 + 0.70*(1.0 - up))*occ*0.55;\n\n    col = alb*(lit + amb);\n\n    // stone still holding the day heat, only readable once it is dark\n    col += vec3(0.075,0.031,0.014)*gWarm*gNight*occ*(0.35 + 0.65*up)*alb;\n    col += vec3(0.85,0.92,1.0)*frostM*spark*(0.9 + 40.0*gMoonI);\n\n    // aerial perspective\n    float fog = 1.0 - exp(-tHit*((kind == 2) ? 0.012 : 0.008));\n    vec3 fogc = skyColor(normalize(rd + vec3(0.0, 0.02, 0.0)), false);\n    col = mix(col, fogc, fog*((kind == 2) ? 1.0 : 0.55));\n  }\n\n  // ---- output ---------------------------------------------------\n  col *= gAdapt*exposure;\n  // Purkinje shift: night vision loses colour and leans blue\n  float lum = dot(col, vec3(0.2126, 0.7152, 0.0722));\n  float scot = gNight*smoothstep(0.16, 0.008, lum);\n  col = mix(col, vec3(lum)*vec3(0.72, 0.86, 1.22), scot*0.62);\n\n  col = vec3(1.0) - exp(-col*1.55);\n  col = pow(max(col, 0.0), vec3(0.4545));\n  float l2 = dot(col, vec3(0.2126, 0.7152, 0.0722));\n  col = clamp(mix(vec3(l2), col, 1.22), 0.0, 1.0);      // saturation\n  col = mix(col, col*col*(3.0 - 2.0*col), 0.45);        // S-curve\n  col *= 1.0 - 0.26*dot(uv, uv);                       // vignette\n  col += (hash21(gl_FragCoord.xy + fract(TIME)*17.0) - 0.5)/220.0;   // dither\n  gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}\n",
    "generator": {
      "model": "claude-opus-5",
      "provider": "anthropic",
      "prompt": null,
      "params": null
    }
  }
}