{
  "license": "CC0-1.0",
  "shader": {
    "id": "oyj85kdj",
    "title": "Cold Storage",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "Claude",
    "status": "active",
    "compile_ok": true,
    "render_hash": "602c4cc010007301",
    "tags": [
      "cd",
      "cds",
      "floppy",
      "floppys",
      "floppy discs",
      "discs",
      "data",
      "storage"
    ],
    "parents": [],
    "author": "u7y1evhf0znj",
    "author_name": "ksin",
    "author_url": "/u/ksin",
    "created_at": "2026-09-08T04:00:21.910Z",
    "poster_url": "/posters/oyj85kdj.avif",
    "stats": {
      "slops": 1,
      "views": 136,
      "forks": 0
    },
    "self": "/api/v1/shaders/oyj85kdj",
    "isf": "/*{\n  \"DESCRIPTION\": \"A shower of CDs and 3.5 inch floppies falling and tumbling in the dark, with the rainbow on every disc solved from the grating equation rather than painted with a hue ramp. The data spiral is a real reflection grating: both the view ray and the light ray are refracted into the polycarbonate (n = 1.58) and walked down the 1.2mm to the metal, and with u the in-plane radial direction and g the tangential groove direction, the component across the grooves gives lambda = n*pitch*|q|/m while the component along them has to vanish for the order to exist at all. That second condition is what puts the rainbow in two opposed lobes; the lamp's angular size is what gives them thickness instead of leaving them curves of measure zero. The bands stack outward because when the first order has run past red into the infrared the second is arriving at blue -- which is what a CD does and what a hue ramp cannot. Colour is the CIE 1931 matching functions, so every wavelength gets the colour it really has, and the lobes are widened by the wavelength bandwidth one pixel covers, so where the bands crowd tighter than the pixels can follow they wash out to white instead of aliasing into confetti. Each object is a torque-free symmetric top with I3 = 2*I1, so its axis precesses about the angular momentum at twice the rate the body turns beneath it and the other way round: Feynman's plate. Because the grating is rotationally symmetric, the body spin moves only the scratches, the thumbprint and the write spot, while the normal swinging round its cone is what sweeps the colour. The same law carries the floppies, and that is not a fudge -- the perpendicular axis theorem gives a flat plate I3 = I1 + I2, so a square plate has I3 = 2*I1 exactly, and 90 by 94 is within four percent of square. Every rate is a whole number of turns per minute, so the whole shower is an exact 60 second loop. Around that: dye tint applied on the way in and out, the mirror band, lead-out and clear rim, arc scratches and wipe marks, a thumbprint that fogs the grating rather than dimming it, dust, and the burn boundary walking outward with a write spot riding it. The floppies are a box and a half-space for the cut corner, with shutter, hub, drive slot and corner holes. Twenty-two printed disc faces from 1988 to 2005 and nine floppy labels -- all of them period STYLES, not copies of anybody's real logo. None of the lettering is lettering either: dashed arcs read as microtext, and a stroke with a sloping baseline, per-letter pen pressure and proper risers and tails reads as handwriting. Three liberties, all noted in the source: the write laser is really 780nm infrared and invisible, the spindle is slowed so the spot reads at 60fps, and the key light is rigged to each object rather than to the room -- one lamp cannot be near-field to a shower half a metre wide, and near-field is exactly what makes the orders stack. Every knob exposed.\",\n  \"CREDIT\": \"CC0\",\n  \"ISFVSN\": \"2\",\n  \"CATEGORIES\": [\"generator\"],\n  \"INPUTS\": [\n    { \"NAME\": \"uSpeed\",      \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,   \"MAX\": 3.0,   \"LABEL\": \"speed\" },\n    { \"NAME\": \"uCamPitch\",   \"TYPE\": \"float\", \"DEFAULT\": 14.0,  \"MIN\": 5.0,   \"MAX\": 85.0,  \"LABEL\": \"camera pitch\" },\n    { \"NAME\": \"uCamYaw\",     \"TYPE\": \"float\", \"DEFAULT\": 0.35,  \"MIN\": -3.15, \"MAX\": 3.15,  \"LABEL\": \"camera yaw\" },\n    { \"NAME\": \"uCamDist\",    \"TYPE\": \"float\", \"DEFAULT\": 480.0, \"MIN\": 200.0, \"MAX\": 1600.0, \"LABEL\": \"camera distance\" },\n    { \"NAME\": \"uFov\",        \"TYPE\": \"float\", \"DEFAULT\": 50.0,  \"MIN\": 18.0,  \"MAX\": 70.0,  \"LABEL\": \"field of view\" },\n    { \"NAME\": \"uLampHeight\", \"TYPE\": \"float\", \"DEFAULT\": 92.0 , \"MIN\": 40.0,  \"MAX\": 600.0, \"LABEL\": \"lamp height\" },\n    { \"NAME\": \"uLampRadius\", \"TYPE\": \"float\", \"DEFAULT\": 60.0 , \"MIN\": 0.0,   \"MAX\": 400.0, \"LABEL\": \"lamp radius\" },\n    { \"NAME\": \"uLampOrbit\",  \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": -6.0,  \"MAX\": 6.0,   \"LABEL\": \"lamp orbits/min\" },\n    { \"NAME\": \"uSourceSize\", \"TYPE\": \"float\", \"DEFAULT\": 0.140, \"MIN\": 0.005, \"MAX\": 0.35,  \"LABEL\": \"lamp size\" },\n    { \"NAME\": \"uLampPower\",  \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,   \"MAX\": 4.0,   \"LABEL\": \"lamp power\" },\n    { \"NAME\": \"uPitch\",      \"TYPE\": \"float\", \"DEFAULT\": 1.60,  \"MIN\": 0.35,  \"MAX\": 2.20,  \"LABEL\": \"track pitch um\" },\n    { \"NAME\": \"uOrders\",     \"TYPE\": \"float\", \"DEFAULT\": 3.0,   \"MIN\": 1.0,   \"MAX\": 3.0,   \"LABEL\": \"orders\" },\n    { \"NAME\": \"uDye\",        \"TYPE\": \"long\",  \"DEFAULT\": 4, \"VALUES\": [0, 1, 2, 3, 4],\n      \"LABELS\": [\"azo (green)\", \"cyanine (blue)\", \"phthalocyanine (gold)\", \"pressed (silver)\", \"mixed\"], \"LABEL\": \"dye\" },\n    { \"NAME\": \"uCount\",      \"TYPE\": \"float\", \"DEFAULT\": 28.0,  \"MIN\": 1.0,   \"MAX\": 28.0,  \"LABEL\": \"objects\" },\n    { \"NAME\": \"uFloppy\",     \"TYPE\": \"float\", \"DEFAULT\": 0.40,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"floppies\" },\n    { \"NAME\": \"uFall\",       \"TYPE\": \"float\", \"DEFAULT\": 6.0,   \"MIN\": 0.0,   \"MAX\": 30.0,  \"LABEL\": \"falls/min\" },\n    { \"NAME\": \"uSpread\",     \"TYPE\": \"float\", \"DEFAULT\": 450.0, \"MIN\": 320.0, \"MAX\": 900.0, \"LABEL\": \"spread\" },\n    { \"NAME\": \"uBurn\",       \"TYPE\": \"float\", \"DEFAULT\": 0.62,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"burn\" },\n    { \"NAME\": \"uBurnSpeed\",  \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,   \"MAX\": 8.0,   \"LABEL\": \"burns/min\" },\n    { \"NAME\": \"uWriteGlow\",  \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,   \"MAX\": 2.0,   \"LABEL\": \"write spot\" },\n    { \"NAME\": \"uScratches\",  \"TYPE\": \"float\", \"DEFAULT\": 0.45,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"scratches\" },\n    { \"NAME\": \"uScuff\",      \"TYPE\": \"float\", \"DEFAULT\": 0.40,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"scuff\" },\n    { \"NAME\": \"uSmudge\",     \"TYPE\": \"float\", \"DEFAULT\": 0.35,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"thumbprint\" },\n    { \"NAME\": \"uDust\",       \"TYPE\": \"float\", \"DEFAULT\": 0.30,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"dust\" },\n    { \"NAME\": \"uWarp\",       \"TYPE\": \"float\", \"DEFAULT\": 0.35,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"warp\" },\n    { \"NAME\": \"uWobble\",     \"TYPE\": \"float\", \"DEFAULT\": 54.0,  \"MIN\": 0.0,   \"MAX\": 80.0,  \"LABEL\": \"wobble deg\" },\n    { \"NAME\": \"uPrecess\",    \"TYPE\": \"float\", \"DEFAULT\": 8.0 ,  \"MIN\": 0.0,   \"MAX\": 60.0,  \"LABEL\": \"wobbles/min\" },\n    { \"NAME\": \"uAniso\",      \"TYPE\": \"float\", \"DEFAULT\": 0.75,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"streak\" },\n    { \"NAME\": \"uGloss\",      \"TYPE\": \"float\", \"DEFAULT\": 0.60,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"gloss\" },\n    { \"NAME\": \"uGain\",       \"TYPE\": \"float\", \"DEFAULT\": 1.50,  \"MIN\": 0.0,   \"MAX\": 3.0,   \"LABEL\": \"spectrum gain\" },\n    { \"NAME\": \"uAmbient\",    \"TYPE\": \"float\", \"DEFAULT\": 0.12,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"ambient\" },\n    { \"NAME\": \"uExposure\",   \"TYPE\": \"float\", \"DEFAULT\": 1.05,  \"MIN\": 0.0,   \"MAX\": 3.0,   \"LABEL\": \"exposure\" },\n    { \"NAME\": \"uBloom\",      \"TYPE\": \"float\", \"DEFAULT\": 0.62,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"bloom\" },\n    { \"NAME\": \"uVignette\",   \"TYPE\": \"float\", \"DEFAULT\": 0.40,  \"MIN\": 0.0,   \"MAX\": 1.0,   \"LABEL\": \"vignette\" }\n  ]\n}*/\n\n#define PI   3.14159265359\n#define TAU  6.28318530718\n#define SPIN (TAU * 53.0 / 60.0)   // spindle, turns per loop\n#define NDISC 28\n\n// Disc geometry, millimetres. A 120mm disc: 15mm hole, clamping\n// area out to ~33mm diameter, the metallised layer starting at\n// the mirror band, program area 50-116mm diameter, then lead-out\n// and a clear rim.\n#define R_OUT    60.0\n#define R_HOLE    7.5\n#define R_CLAMP  22.0    // clear polycarbonate ends, metal begins\n#define R_MIRROR 25.0    // ungrooved mirror band ends, data begins\n#define R_DATA   58.0    // program area ends\n#define R_RIM    58.6    // lead-out ends, clear rim begins\n#define TH        1.2    // polycarbonate thickness read through\n#define NPC       1.58   // refractive index of polycarbonate\n#define ETA      (1.0 / NPC)\n\n// State of the disc currently being shaded. A mutable global spares\n// threading it through every shading function; it is set once, in\n// the entry point, after the nearest disc has been found.\nmat3  gM;       // disc space -> world, for the disc being shaded\nfloat gLabel;   // which printed face it carries\nfloat gDye;     // which recording dye\nfloat gBurn;    // where its burn started\nfloat gKind;    // 0 a disc, 1 a floppy\n\n// ---- hashes and a little value noise ----\nfloat hash11(float p){\np = fract(p * 0.1031);\np *= p + 33.33;\np *= p + p;\nreturn fract(p);\n}\n\nfloat hash12(vec2 p){\nvec3 p3 = fract(vec3(p.xyx) * 0.1031);\np3 += dot(p3, p3.yzx + 33.33);\nreturn fract((p3.x + p3.y) * p3.z);\n}\n\nfloat vnoise(vec2 p){\nvec2 i = floor(p), f = fract(p);\nf = f * f * (3.0 - 2.0 * f);\nreturn mix(mix(hash12(i), hash12(i + vec2(1.0, 0.0)), f.x),\nmix(hash12(i + vec2(0.0, 1.0)), hash12(i + vec2(1.0, 1.0)), f.x), f.y);\n}\n\n// wrap an angle difference into [-PI, PI] without a modulo\nfloat wrapPi(float a){ return a - TAU * floor(a / TAU + 0.5); }\n\n// ---- CIE 1931 colour matching, analytic and blurrable ----\n// Wyman, Sloan and Shirley multi-lobe piecewise Gaussians. The\n// extra argument convolves every lobe with a Gaussian of that\n// width, which is how a pixel that covers a wide band of\n// wavelengths gets to desaturate instead of alias. Widening a\n// Gaussian and keeping its integral means scaling the peak by\n// s/sqrt(s*s + b*b), which is the factor out front.\nfloat pgB(float x, float mu, float s1, float s2, float b){\nfloat s = (x < mu) ? s1 : s2;\nfloat w = sqrt(s * s + b * b);\nfloat t = (x - mu) / w;\nreturn (s / w) * exp(-0.5 * t * t);\n}\n\nvec3 xyzBar(float w, float b){\nreturn vec3(\n1.056 * pgB(w, 599.8, 37.9, 31.0, b) + 0.362 * pgB(w, 442.0, 16.0, 26.7, b)\n- 0.065 * pgB(w, 501.1, 20.4, 26.2, b),\n0.821 * pgB(w, 568.8, 46.9, 40.5, b) + 0.286 * pgB(w, 530.9, 16.3, 31.1, b),\n1.217 * pgB(w, 437.0, 11.8, 36.0, b) + 0.681 * pgB(w, 459.0, 26.0, 13.8, b));\n}\n\nvec3 xyz2rgb(vec3 c){\nreturn vec3( 3.2406 * c.x - 1.5372 * c.y - 0.4986 * c.z,\n-0.9689 * c.x + 1.8758 * c.y + 0.0415 * c.z,\n0.0557 * c.x - 0.2040 * c.y + 1.0570 * c.z);\n}\n\n// A single wavelength sits outside sRGB, so the naive transform\n// comes back with a negative channel. Adding just enough white\n// to clear it is the standard desaturate-into-gamut move, and it\n// keeps the spectrum vivid instead of clipping it to mud.\nvec3 toGamut(vec3 rgb){\nfloat m = min(min(rgb.r, rgb.g), rgb.b);\nif (m < 0.0) rgb -= m;\nreturn max(rgb, 0.0);\n}\n\n// ---- the lamp ----\nvec3 lampPos(float t){\nfloat a = t * uLampOrbit * TAU / 60.0 + 2.35;\nreturn vec3(cos(a) * uLampRadius, uLampHeight, sin(a) * uLampRadius);\n}\n\n// ---- surface detail ----\n// The height gradient of everything that is not the data spiral\n// -- warp, stacking ring, scratches, thumbprint -- plus broadband\n// scatter and how far the grating is locally decohered (a smudge\n// fogs the rainbow, it does not just dim it).\nvoid surfaceDetail(vec2 pd, float r, float th, float fp,\nout vec3 N, out float scatter, out float decoh, out float rough){\nvec2 g = vec2(0.0);\nscatter = 0.0;\ndecoh   = 0.0;\nrough   = 0.0;\n\n// The disc is not flat. Two low-frequency lobes, differentiated\n// by hand so the normal is exact rather than sampled. A few\n// hundredths of a radian is enough: the band position moves by\n// 2*slope in q, which is more than one band width.\nfloat A = uWarp * 2.0;\nfloat sx = sin(0.031 * pd.x + 1.3), cx = cos(0.031 * pd.x + 1.3);\nfloat sz = sin(0.027 * pd.y - 0.7), cz = cos(0.027 * pd.y - 0.7);\nfloat s2 = sin(0.017 * pd.x - 0.9), c2 = cos(0.017 * pd.x - 0.9);\nfloat s3 = sin(0.021 * pd.y + 2.2), c3 = cos(0.021 * pd.y + 2.2);\ng.x += A * (0.031 * cx * cz + 0.55 * 0.017 * c2 * s3);\ng.y += A * (-0.027 * sx * sz + 0.55 * 0.021 * s2 * c3);\n\n// the moulded stacking ring in the clamping area\nfloat dsr = r - 18.25;\nfloat ring = 0.075 * exp(-dsr * dsr / 1.96);\nfloat dring = -2.0 * dsr / 1.96 * ring;\n\nvec2 uhF = (r > 1e-4) ? pd / r : vec2(1.0, 0.0);   // radial, flat\nvec2 ghF = vec2(-uhF.y, uhF.x);                    // tangential, flat\ng += uhF * dring;\n\n// Arc scratches. The groove of a circumferential scratch runs\n// tangentially, so its cross-section slope is purely radial and\n// comes out in closed form from the Gaussian profile — no finite\n// differences, no extra noise lookups.\nfor (int i = 0; i < 10; i++){\nfloat fi = float(i);\nif (hash11(fi * 5.51 + 3.0) > uScratches + 0.12) continue;\nfloat rc = mix(12.0, 59.0, hash11(fi * 3.71 + 11.0));\nfloat ac = hash11(fi * 7.13 + 5.0) * TAU;\nfloat aw = mix(0.18, 1.7, hash11(fi * 2.37 + 19.0));\nfloat w0 = mix(0.10, 0.42, hash11(fi * 1.90 + 2.0));\n// A scratch narrower than a pixel must not be drawn narrower than a\n// pixel: sampled sharp it breaks into a dashed line. Widen it to the\n// footprint and dim it by the same factor, which is what filtering it\n// properly would have done.\nfloat w  = max(w0, fp * 0.9);\nfloat da = wrapPi(th - ac);\nfloat am = smoothstep(aw, aw * 0.5, abs(da));\nif (am < 0.003) continue;\nfloat dr = r - rc + 1.1 * sin(da * 2.7 + fi);\nfloat e  = exp(-dr * dr / (w * w));\nfloat k  = w0 / w;\ng += uhF * (-2.0 * dr / (w * w)) * e * 0.012 * am;\nscatter += e * am * 0.55 * k;\ndecoh   += e * am * 0.35 * k;\n}\n\n// Radial wipe marks. Same trick, rotated: the cross-section\n// coordinate is arc length, so the slope lands on the tangent.\nfor (int i = 0; i < 6; i++){\nfloat fi = float(i);\nif (hash11(fi * 9.13 + 41.0) > uScratches * 0.75) continue;\nfloat ac = hash11(fi * 4.41 + 7.0) * TAU;\nfloat w0 = mix(0.09, 0.30, hash11(fi * 6.17 + 13.0));\nfloat w  = max(w0, fp * 0.9);\nfloat r0 = mix(10.0, 34.0, hash11(fi * 2.71 + 23.0));\nfloat r1 = r0 + mix(9.0, 34.0, hash11(fi * 8.09 + 31.0));\nfloat rm = smoothstep(r0, r0 + 3.0, r) * smoothstep(r1, r1 - 3.0, r);\nif (rm < 0.003) continue;\nfloat dl = r * wrapPi(th - ac);\nfloat e  = exp(-dl * dl / (w * w));\nfloat k  = w0 / w;\ng += ghF * (-2.0 * dl / (w * w)) * e * 0.010 * rm;\nscatter += e * rm * 0.45 * k;\ndecoh   += e * rm * 0.30 * k;\n}\n\n// Fine circumferential scuff. These are far below a pixel, so\n// they arrive as roughness and a milky sheen, not as normals.\nfloat sc = vnoise(vec2(r * 1.1, th * 9.0)) * 0.65 + vnoise(vec2(r * 4.3, th * 21.0)) * 0.35;\nrough   += uScuff * (0.25 + 0.75 * sc);\nscatter += uScuff * sc * 0.09;\n\n// A thumbprint. The ridges tilt the surface, and the grease\n// between them scrambles the grating: decoh widens both the\n// conical acceptance and the wavelength blur further down, so\n// the rainbow goes pale under it instead of merely dimmer.\nvec2 df = pd - vec2(24.0, -21.0);\nfloat rf = length(df);\nfloat sm = uSmudge * exp(-rf * rf / 361.0);\nif (sm > 0.002){\nfloat ang = atan(df.y, df.x);\nfloat rgF = 11.4;                       // ridges about 0.55mm apart\nfloat ph  = rf * rgF + 2.2 * sin(ang * 1.7 + 0.7) + 1.3 * sin(rf * 0.35);\nfloat dph = rgF + 0.455 * cos(rf * 0.35);\nfloat fpd = fp * rgF;\nfloat damp = 1.0 / (1.0 + 4.0 * fpd * fpd);\ng += (df / max(rf, 1e-4)) * (-0.0028 * sm * cos(ph) * dph * damp);\ndecoh   += sm * 0.60;\nscatter += sm * 0.12;\nrough   += sm * 0.5;\n}\n\n// Dust: sparse, in cells about a millimetre across.\nvec2 ci = floor(pd * 0.85);\nif (hash12(ci) > 1.0 - 0.11 * uDust){\nvec2 c  = vec2(hash12(ci + 7.1), hash12(ci + 3.3));\nfloat d = length(fract(pd * 0.85) - c);\nfloat sz2 = mix(0.06, 0.17, hash12(ci + 11.0));\nfloat sp = smoothstep(sz2, sz2 * 0.35, d);\nscatter += sp * 1.1;\ndecoh   += sp * 0.9;\n}\n\nN = normalize(vec3(-g.x, 1.0, -g.y));\n}\n\n// ---- the grating equation ----\n// Returns (q, s, data-layer x, data-layer z). Both rays are bent\n// into the polycarbonate first and the view ray is walked down\n// the 1.2mm to the metal, so the point the spectrum is read at is\n// not the point the eye is looking at — that offset is the real\n// parallax between the surface highlight and the rainbow.\nvec4 gratingQS(vec3 p, vec3 rd, vec3 N, vec3 lp){\nvec3 Vi = refract(rd, N, ETA);\nvec3 pd3 = p + Vi * (TH / max(-Vi.y, 0.02));\nvec3 Li = refract(-normalize(lp - p), N, ETA);\nvec3 Vo = -Vi, Lo = -Li;\nvec3 rad = vec3(pd3.x, 0.0, pd3.z);\nif (dot(rad, rad) < 1e-6) rad = vec3(1.0, 0.0, 0.0);\nvec3 gh = normalize(cross(N, rad));   // groove direction, tangential\nvec3 uh = cross(gh, N);               // across the grooves, radial\nreturn vec4(dot(Lo, uh) + dot(Vo, uh),\ndot(Lo, gh) + dot(Vo, gh),\npd3.x, pd3.z);\n}\n\n// ---- the spectrum ----\n// dq and ds are the footprint of one pixel in (q, s). ds widens\n// the conical acceptance; dq becomes the wavelength bandwidth the\n// pixel covers, which is fed straight into the CIE lobes. That is\n// the whole anti-aliasing story: where the bands crowd together\n// faster than the pixels can follow, they desaturate to white on\n// their own rather than turning to confetti.\nvec3 spectrumXYZ(float q, float s, float dq, float ds, float pitchNm, float decoh){\nfloat sig = sqrt(uSourceSize * uSourceSize + ds * ds + decoh * decoh * 0.010);\nfloat cone = exp(-0.5 * (s * s) / (sig * sig));\nif (cone < 0.0015) return vec3(0.0);\nfloat nd = NPC * pitchNm;\nfloat aq = abs(q);\nfloat blur0 = nd * (dq + decoh * 0.10) + 4.0;\nvec3 xyz = vec3(0.0);\nfor (int m = 1; m <= 3; m++){\nfloat fm = float(m);\nif (fm > uOrders + 0.5) break;\nfloat lam  = nd * aq / fm;\nfloat blur = blur0 / fm;\nif (lam < 330.0 - blur || lam > 820.0 + blur) continue;\nxyz += xyzBar(lam, blur) * pow(0.38, fm - 1.0);\n}\nreturn xyz * cone;\n}\n\n// ---- lobes ----\n// Ward, anisotropic. The grooves make the surface smooth along g\n// and corrugated across u, so the highlight stretches along u —\n// the zeroth order on a CD is a radial streak, not a dot.\nfloat wardAniso(vec3 Lo, vec3 Vo, vec3 N, vec3 uh, vec3 gh, float ax, float ay){\nvec3 h = Lo + Vo;\nfloat hl = length(h);\nif (hl < 1e-4) return 0.0;\nh /= hl;\nfloat hn = dot(h, N);\nif (hn < 1e-3) return 0.0;\nfloat hu = dot(h, uh) / hn;\nfloat hg = dot(h, gh) / hn;\nreturn exp(-(hu * hu / (ax * ax) + hg * hg / (ay * ay))) / (4.0 * PI * ax * ay);\n}\n\nfloat ggx(vec3 L, vec3 V, vec3 N, float a){\nvec3 h = normalize(L + V);\nfloat nh = max(dot(N, h), 0.0);\nfloat d  = nh * nh * (a * a - 1.0) + 1.0;\nreturn a * a / (PI * d * d + 1e-6);\n}\n\n// Round-trip transmission through the recording dye, and what the\n// written region does to it. A pressed disc has no dye at all.\nvec4 dyeLayer(float burned){\nif (gDye > 2.5) return vec4(1.0);\nvec3 fresh = (gDye < 0.5) ? vec3(0.62, 0.93, 0.71)   // azo, green\n: (gDye < 1.5) ? vec3(0.66, 0.88, 1.00)   // cyanine, blue-green\n: vec3(1.00, 0.94, 0.72);  // phthalocyanine, faint gold\nreturn vec4(mix(fresh, fresh * vec3(0.86, 0.75, 0.67), burned),\nmix(1.0, 0.84, burned));\n}\n\n// ---- the studio ----\n// A FLAT mirror shows only a narrow cone of directions, so light\n// it with smooth gradients and every pixel lands on the same patch\n// of gradient: the disc comes back one flat colour and reads as\n// frosted plastic. It was never the environment's STRENGTH that\n// was wrong, it had no structure at the only angular scale a flat\n// mirror can resolve. Hence ceiling strip lights, narrow enough\n// that a disc catches a bar or two and sweeps the rest as it\n// tumbles.\nfloat ceilingStrips(vec3 d){\nif (d.y < 0.05) return 0.0;\nvec3 dn = d / max(d.y, 0.05);            // onto the ceiling plane\nfloat u = (dn.x + dn.z) * 0.7071;        // across the tubes\nfloat v = (dn.x - dn.z) * 0.7071;        // along them\nfloat g = 0.0;\nfor (int i = 0; i < 5; i++){\nfloat t = u - (float(i) - 2.0) * 0.30;\ng += exp(-t * t / 0.0060);\n}\nreturn g * smoothstep(2.6, 1.1, abs(v)) * smoothstep(0.05, 0.26, d.y);\n}\n\nvec3 studioEnv(vec3 d){\nfloat h = clamp(d.y * 0.8 + 0.5, 0.0, 1.0);\nvec3 c = mix(vec3(0.006, 0.006, 0.009), vec3(0.026, 0.028, 0.038), h * h);\nc += vec3(1.00, 0.98, 0.94) * ceilingStrips(d) * 0.34;\nc += vec3(0.24, 0.25, 0.31) * smoothstep(0.60, 0.02, abs(d.y));\nreturn c * (0.30 + uAmbient * 3.2);\n}\n\n// Behind the discs is nothing at all. The room they REFLECT is\n// still there in studioEnv -- what a set separates is the paper\n// behind the subject from the panels lighting it, and here the\n// paper is simply black.\n\nvec3 planeHit(vec3 ro, vec3 rd){\nreturn ro + rd * ((TH * 0.5 - ro.y) / min(rd.y, -1e-4));\n}\n\n// ---- the disc ----\n// Everything from here down is in DISC SPACE: +y is the disc's\n// own normal and the body turns with it, so the scratches, the\n// thumbprint and the burn all sit still in these coordinates\n// while the disc tumbles. That is not a shortcut, it is the\n// point -- the grating is rotationally symmetric, so spinning\n// the disc moves the scratches and nothing else. All the motion\n// in the rainbow comes from the normal changing direction.\n//\n// rdx / rdy are the ray directions one pixel over; the mirrored\n// pair is reconstructed from them, so the footprint gets both a\n// first and a second difference.\nvec3 shadeDisc(vec3 ro, vec3 rd, vec3 rdx, vec3 rdy,\nvec3 lp, float t, out vec3 glow){\nglow = vec3(0.0);\n\nvec3 p  = planeHit(ro, rd);\nvec3 px = planeHit(ro, rdx);\nvec3 py = planeHit(ro, rdy);\nfloat fp = 0.5 * (length(px.xz - p.xz) + length(py.xz - p.xz));\n\nvec3  N;\nfloat scatter, decoh, rough;\nfloat r0 = length(p.xz);\nsurfaceDetail(p.xz, r0, atan(p.z, p.x), fp, N, scatter, decoh, rough);\n\nvec4 qs = gratingQS(p, rd, N, lp);\nvec2 pd = qs.zw;\nfloat r  = length(pd);\nfloat th = atan(pd.y, pd.x);\n\n// Footprint, against the FLAT normal: what is wanted is the\n// geometric rate of change of (q, s) across a pixel, and four\n// more surfaceDetail calls would fold the scratches into their\n// own blur. The second difference is not optional -- at a\n// stationary point of q the first one vanishes while the field\n// is still moving under the pixel, so the linear estimate\n// alone lets exactly the busiest region alias.\nvec3 FLAT = vec3(0.0, 1.0, 0.0);\nvec3 rmx = normalize(2.0 * rd - rdx);\nvec3 rmy = normalize(2.0 * rd - rdy);\nvec4 c0  = gratingQS(p, rd, FLAT, lp);\nvec4 cxp = gratingQS(px, rdx, FLAT, lp);\nvec4 cyp = gratingQS(py, rdy, FLAT, lp);\nvec4 cxm = gratingQS(planeHit(ro, rmx), rmx, FLAT, lp);\nvec4 cym = gratingQS(planeHit(ro, rmy), rmy, FLAT, lp);\nfloat dq = 0.5 * (abs(cxp.x - cxm.x) + abs(cyp.x - cym.x))\n+ 0.5 * (abs(cxp.x + cxm.x - 2.0 * c0.x)\n+ abs(cyp.x + cym.x - 2.0 * c0.x));\nfloat ds = 0.5 * (abs(cxp.y - cym.y) + abs(cyp.y - cym.y))\n+ 0.5 * (abs(cxp.y + cxm.y - 2.0 * c0.y)\n+ abs(cyp.y + cym.y - 2.0 * c0.y));\nfloat w  = max(fp * 0.7, 0.03);\n\n// ---- radial zones ----\nfloat mMetal = smoothstep(R_CLAMP - w, R_CLAMP + w, r)\n* smoothstep(R_RIM + w, R_RIM - w, r);\nfloat mData  = smoothstep(R_MIRROR - w, R_MIRROR + w, r)\n* smoothstep(R_DATA + w, R_DATA - w, r);\nfloat mLead  = smoothstep(R_DATA - w, R_DATA + w, r)\n* smoothstep(R_RIM + w, R_RIM - w, r);\nfloat grating = mData + 0.5 * mLead;          // the mirror band has no grooves\n\n// ---- the burn ----\nfloat phase = fract(gBurn + t * uBurnSpeed / 60.0);\nfloat rBurn = mix(R_MIRROR, R_DATA, phase);\nfloat burned = smoothstep(rBurn + max(w, 0.22), rBurn - max(w, 0.22), r) * mData;\nvec4 dye = dyeLayer(burned);\n\n// ---- light ----\nvec3  dl = lp - p;\nfloat d2 = dot(dl, dl);\nvec3  L  = dl * inversesqrt(d2);\nfloat irr = uLampPower * 30000.0 / max(d2, 1.0);\nfloat ndl = max(dot(N, L), 0.0);\n\nvec3 Vi = refract(rd, N, ETA), Li = refract(-L, N, ETA);\nvec3 Vo = -Vi, Lo = -Li;\nvec3 rad = vec3(pd.x, 0.0, pd.y);\nif (dot(rad, rad) < 1e-6) rad = vec3(1.0, 0.0, 0.0);\nvec3 gh = normalize(cross(N, rad));\nvec3 uh = cross(gh, N);\n\n// ---- the diffracted orders ----\nfloat pitchNm = uPitch * 1000.0;\nvec3 spec = toGamut(xyz2rgb(spectrumXYZ(qs.x, qs.y, dq, ds, pitchNm, decoh))) * 0.62;\n\n// ---- the zeroth order ----\n// Rough across the grooves, smooth along them, so the specular\n// reflection of the lamp arrives as a radial streak.\nfloat ax = mix(0.040, 0.13, uAniso) + rough * 0.060;\nfloat ay = mix(0.055, 0.014, uAniso) + rough * 0.030;\nfloat zer = wardAniso(Lo, Vo, N, uh, gh, ax, ay) * ndl;\nvec3 alu = vec3(0.94, 0.95, 0.97);\n\n// ---- the write spot ----\n// The head is fixed in space while the disc turns under it, so\n// in these body coordinates it is the spot that moves.\nvec3 write = vec3(0.0);\nif (uWriteGlow > 0.002){\nfloat aw = -t * SPIN;\nfloat da = wrapPi(th - aw);\nfloat dr = r - rBurn;\nfloat radial = exp(-dr * dr / 0.55);\nfloat head = exp(-da * da * 900.0);\nfloat tail = (da > 0.0) ? exp(-da * 2.4) : 0.0;\nwrite = (vec3(1.00, 0.40, 0.16) * head * 2.6\n+ vec3(1.00, 0.52, 0.22) * tail * 0.22) * radial * uWriteGlow * mData;\n}\n\n// ---- the polycarbonate front face ----\nvec3 envR = studioEnv(gM * reflect(rd, N));\nfloat fres = 0.05 + 0.95 * pow(1.0 - max(dot(N, -rd), 0.0), 5.0);\nfloat topA = mix(0.020, 0.14, 1.0 - uGloss) + rough * 0.050;\nfloat top  = ggx(L, -rd, N, topA) * ndl * fres;\n\n// ---- broadband scatter off scratches and dust ----\nfloat broad = pow(max(dot(reflect(-L, N), -rd), 0.0), 6.0);\nfloat scat = scatter * (0.20 + broad * 2.6) * ndl;\n\n// ---- put the disc together ----\nvec3 metalCol = ((spec * grating * uGain * ndl * 1.35\n+ alu * zer * 0.038) * irr\n+ alu * envR) * dye.rgb * dye.a\n+ write;\n\n// Where there is no metal the disc is a window: clear\n// polycarbonate with the backdrop behind it.\nvec3 clearCol = envR * 0.09;   // clear plastic over a black backdrop\n\nvec3 col = mix(clearCol, metalCol, mMetal);\ncol += envR * fres * 0.55;\ncol += vec3(0.97, 0.97, 1.00) * top * irr * 0.075;\ncol += vec3(1.00, 0.99, 0.96) * scat * irr * 0.030;\n\n// Veiling glare: the same two lobes evaluated far wider. It is\n// a real spatial spread computed per pixel, which is the only\n// kind available without a blur pass.\nif (uBloom > 0.002){\nfloat gz = wardAniso(Lo, Vo, N, uh, gh, ax * 4.5 + 0.25, ay * 6.0 + 0.13) * ndl;\nfloat gt = ggx(L, -rd, N, 0.62) * ndl * fres * 0.5;\nvec3 gs = toGamut(xyz2rgb(spectrumXYZ(qs.x, qs.y, dq, ds * 2.6, pitchNm,\ndecoh + 0.35))) * 0.62;\nglow = (gs * grating * uGain * 0.12 * dye.rgb + alu * gz * 0.038\n+ vec3(0.97, 0.97, 1.0) * gt * 0.075) * irr + write * 0.5;\n// The glare picks up glints off the same dust and scratches that\n// scattered the light to begin with, so it breaks into points\n// rather than arriving as a smooth wash. Seeded in the disc's own\n// frame, so they ride the body spin, widened by the footprint so\n// they cannot alias, and twinkling a whole number of times a\n// minute so the loop still closes.\nvec2 sg = pd * 0.15;   // ~7mm cells, so a glint is a glint\nvec2 si = floor(sg);\nvec2 sc = vec2(hash12(si + 7.7), hash12(si + 4.1)) - 0.5;\nfloat sd = length(fract(sg) - 0.5 - sc * 0.6);\nfloat tw = 0.55 + 0.45 * sin(TAU * 13.0 * t / 60.0 + hash12(si) * 44.0);\nglow *= 1.0 + step(0.74, hash12(si + 2.3)) * tw\n* smoothstep(0.23 + fp * 0.06, 0.02, sd) * 4.5;\n}\nreturn col;\n}\n\n// ---- the printed sides ----\n// Faces from 1988 to 2005. The artwork on a CD-R sits above the\n// reflective layer, so a face is only in shot when its tumble\n// carries it past edge-on -- which is most of them, most of the\n// time, once it is raining.\n//\n// None of the lettering is lettering: dashed arcs read as\n// microtext, a wobbling stroke broken into runs reads as\n// handwriting. Both sit on ARCS, spaced in arc length so the pitch\n// stays even -- a label turns with its disc, and a rectangular\n// block of type looks arbitrary at every angle that puts it\n// through, while type on an arc looks deliberate at all of them.\nfloat dashRing(float r, float a, float rc, float w, float n, float duty, float fp){\nfloat band = smoothstep(w + fp, w - fp * 0.5, abs(r - rc));\nfloat d = fract(a * n / TAU);\nreturn band * smoothstep(duty + 0.10, duty - 0.10, d);\n}\n\n// two overlapping discs, one cutting the other: the swoosh\nfloat crescent(vec2 p, vec2 c1, float r1, vec2 c2, float r2, float aa){\nreturn smoothstep(r1 + aa, r1 - aa, length(p - c1))\n* smoothstep(r2 - aa, r2 + aa, length(p - c2));\n}\n\n// Frutiger bubbles, with the rim highlight that made them bubbles\nfloat bubbles(vec2 p, float seed, float fp, out float rim){\nfloat m = 0.0;\nrim = 0.0;\nfor (int i = 0; i < 12; i++){\nfloat fi = float(i) + seed;\nvec2 c = vec2(hash11(fi * 3.11 + 1.0), hash11(fi * 7.77 + 4.0)) * 104.0 - 52.0;\nfloat rr = mix(3.5, 13.0, hash11(fi * 5.23 + 9.0));\nfloat d = length(p - c);\nfloat aa = max(fp, 0.6);\nm   += smoothstep(rr + aa, rr - aa, d) * 0.55;\nrim += smoothstep(rr + aa, rr - aa, d) * smoothstep(rr - 3.4 - aa, rr - 1.2, d);\n}\nreturn min(m, 1.0);\n}\n\n// polka dots on a jittered grid\nfloat polka(vec2 p, float cell, float rad, float fp){\nvec2 gi = floor(p / cell);\nvec2 gf = fract(p / cell) - 0.5;\nvec2 j = (vec2(hash12(gi), hash12(gi + 5.3)) - 0.5) * 0.45;\nreturn smoothstep(rad + fp / cell, rad * 0.72, length(gf - j));\n}\n\n// evenly spaced lines, for grids\nfloat lineAt(float x, float period, float w, float fp){\nreturn smoothstep(w + fp, max(w - fp, 0.0),\nabs(mod(x, period) - period * 0.5));\n}\n\n// Type set on an arc: letters are radial dashes of uneven height,\n// evenly spaced in ARC LENGTH so the pitch stays constant.\nfloat arcText(float r, float a, float rc, float h, float a0, float span,\nfloat pitch, float seed, float fp){\nfloat da = wrapPi(a - a0);\nfloat win = smoothstep(span, span * 0.86, abs(da));\nif (win < 0.002) return 0.0;\nfloat sl = da * rc;\nfloat gi = floor(sl / pitch);\nfloat g  = fract(sl / pitch);\nfloat hh = h * mix(0.60, 1.0, hash11(gi * 1.7 + seed));\nreturn win * smoothstep(hh + fp, hh - 0.35, abs(r - rc))\n* smoothstep(0.76, 0.60, g) * smoothstep(0.03, 0.17, g);\n}\n\n// A marker scrawl, same idea: a fat wobbling stroke that follows\n// the annulus and breaks into runs of ink. Handwriting moves along\n// its line far more than across it, so the wobble stays low.\nfloat arcMarker(float r, float a, float rc, float a0, float span, float fp){\nfloat da = wrapPi(a - a0);\nfloat win = smoothstep(span, span * 0.90, abs(da));\nif (win < 0.002) return 0.0;\nfloat sl = da * rc;\nfloat wob = 2.5 * sin(sl * 0.21) + 1.3 * sin(sl * 0.46 + 1.1)\n+ 0.45 * sin(sl * 0.88 + 2.2);\nfloat run = smoothstep(0.13, 0.26, fract(sl * 0.030 + 0.2));\nreturn win * run * smoothstep(2.1 + fp, 0.8, abs(r - rc - wob));\n}\n\n// A track list. The lines crossing the middle break in two on\n// their own, because the clamping area carries no ink -- what\n// happens when you write across a disc with a hole in it. Straight\n// here, where arcs were right for a wordmark: a list IS a\n// left-aligned block.\nfloat mixtape(vec2 p, float fp, out float blue){\nfloat m = 0.0;\nblue = 0.0;\nfor (int i = 0; i < 8; i++){\nfloat fi = float(i);\nfloat title = (fi < 0.5) ? 1.0 : 0.0;\nfloat ph = fi * 1.37;\nfloat x0 = mix(-46.0, -34.0, title);\n// Track names run most of the way across. The hub cuts the middle\n// rows in two, which is the point.\nfloat len = mix(mix(36.0, 84.0, hash11(fi * 3.7 + 2.0)), 56.0, title);\nfloat x = p.x - x0;\n// Nobody writes level. A per-line slope is the single strongest cue\n// that a row of marks is handwriting rather than a row of marks.\nfloat slope = (hash11(fi * 8.3 + 4.0) - 0.5) * 0.13;\nfloat base = 41.0 - fi * 11.0 + slope * x\n+ 0.75 * sin(x * 0.26 + ph) + 0.35 * sin(x * 0.55 + ph * 2.1);\nfloat d = p.y - base;\n// Letters ~2.7mm apart, each with its own pen pressure.\nfloat gi = floor(x / 2.7);\nfloat g  = fract(x / 2.7);\nfloat hA = hash11(gi * 1.7 + ph);\nfloat hB = hash11(gi * 3.9 + ph * 1.3);\nfloat w = mix(1.15, 1.75, hA) * mix(1.0, 1.75, title);\nfloat run = smoothstep(0.20, 0.36, fract(x * 0.055 + fi * 0.41));\nfloat span = smoothstep(len, len - 4.0, x) * smoothstep(-3.0, 1.0, x);\nfloat line = smoothstep(w + fp, w * 0.35, abs(d)) * run * span;\n// Ascenders and descenders: what stop a wobbling line\n// reading as a wobbling line.\nfloat tick = smoothstep(0.80 + fp, 0.25, abs(g - 0.5) * 2.7) * run * span;\nline = max(line, tick * step(0.68, hA) * smoothstep(3.8, 0.0, d) * step(0.0, d));\nline = max(line, tick * step(0.84, hB) * smoothstep(-2.9, 0.0, d) * step(d, 0.0));\n// the number somebody always puts in front of a track\nline = max(line, (1.0 - title)\n* smoothstep(1.1 + fp, 0.35, abs(x - 2.0))\n* smoothstep(2.0 + fp, 0.7, abs(d)));\n// one pen ran out and got swapped for a biro\nif (fi > 0.5 && hash11(fi * 5.9 + 11.0) > 0.68) blue = max(blue, line);\nelse m = max(m, line);\n}\n// the title gets underlined, because it always does\nm = max(m, smoothstep(1.3 + fp, 0.4, abs(p.y - 35.4 - 0.8 * sin(p.x * 0.12)))\n* smoothstep(-36.0, -32.0, p.x) * smoothstep(22.0, 16.0, p.x));\nreturn m;\n}\n\n// the spec band every recordable disc carries\nvec3 specBand(vec3 c, float r, float a, vec3 ink, float fp){\nc = mix(c, ink, dashRing(r, a, 25.5, 1.0, 190.0, 0.5, fp) * 0.90);\nc = mix(c, ink, dashRing(r, a, 55.5, 0.8, 260.0, 0.5, fp) * 0.75);\nreturn mix(c, ink, smoothstep(0.75 + fp, 0.25, abs(r - 52.5)) * 0.85);\n}\n\n// Returns the printed albedo; `gloss` comes back as how shiny the\n// lacquer is there, because ink is flatter than bare stock.\nvec3 labelArt(vec2 lp, float r, float a, float fp, out float gloss){\nfloat d = gLabel;\nfloat aa = max(fp, 0.5);\nfloat paper = 0.80 + 0.16 * vnoise(lp * 1.7);\nvec3 c = vec3(paper);\ngloss = 0.62;\n\nif (d < 0.5){\n// ---- 0: inkjet Y2K. Diagonal wash, swooshes, bubbles. ----\nfloat diag = smoothstep(-46.0, 16.0, lp.x * 0.70 + lp.y * 0.71);\nvec3 wash = mix(vec3(0.05, 0.66, 0.95), vec3(0.58, 0.08, 0.74),\n0.5 + 0.5 * cos(a - 0.35));\nwash = mix(wash, vec3(0.03, 0.12, 0.60), smoothstep(20.0, 58.0, r) * 0.60);\nc = mix(c, wash, diag * 0.96);\nc = mix(c, vec3(0.06, 0.84, 0.99),\ncrescent(lp, vec2(7.0, 1.0), 47.0, vec2(-9.0, -25.0), 51.0, aa) * 0.85);\nc = mix(c, vec3(0.98, 1.00, 1.00),\ncrescent(lp, vec2(-2.0, -12.0), 57.0, vec2(-28.0, -50.0), 62.0, aa) * 0.88);\nvec3 warm = mix(vec3(0.99, 0.52, 0.07), vec3(0.90, 0.11, 0.46),\nsmoothstep(18.0, 56.0, r));\nc = mix(c, warm,\ncrescent(lp, vec2(16.0, 26.0), 48.0, vec2(34.0, 54.0), 50.0, aa) * 0.90);\nvec2 bp = lp - vec2(-24.0, 21.0);\nfloat bd = length(bp);\nc = mix(c, vec3(1.0), clamp((pow(max(sin(atan(bp.y, bp.x) * 9.0), 0.0), 7.0)\n* exp(-bd * 0.055) + exp(-bd * bd * 0.012)) * 0.9, 0.0, 1.0));\nvec2 hc = fract(lp * 0.30) - 0.5;\nc = mix(c, vec3(0.05, 0.10, 0.30), smoothstep(0.36, 0.16, length(hc))\n* smoothstep(0.30, 0.85, cos(a + 1.15)) * smoothstep(24.0, 44.0, r) * 0.42);\nfloat rim;\nfloat bm = bubbles(lp, 0.0, fp, rim);\nc = mix(c, c * 1.30 + vec3(0.07, 0.13, 0.18), bm * 0.62);\nc = mix(c, vec3(1.0), clamp(rim, 0.0, 1.0) * 0.85);\nc = specBand(c, r, a, vec3(0.05, 0.07, 0.16), fp);\nc = mix(c, vec3(0.03, 0.05, 0.13), arcText(r, a, 41.5, 3.1, -1.05, 0.60, 3.1, 4.0, fp) * 0.95);\nc = mix(c, vec3(0.10, 0.15, 0.34), arcText(r, a, 33.5, 1.15, -1.05, 0.52, 1.5, 11.0, fp) * 0.85);\ngloss = 0.74;\nreturn c;\n}\nif (d < 1.5){\n// ---- 1: silver stock, written on with a marker ----\nc = vec3(0.76, 0.76, 0.73) * (0.88 + 0.18 * vnoise(lp * 2.3));\nc = mix(c, vec3(0.10, 0.13, 0.30), smoothstep(1.2 + fp, 0.4, abs(r - 27.0)) * 0.9);\nc = mix(c, vec3(0.10, 0.13, 0.30), dashRing(r, a, 24.0, 0.9, 150.0, 0.5, fp) * 0.8);\nc = mix(c, vec3(0.16, 0.18, 0.22), dashRing(r, a, 55.0, 0.8, 210.0, 0.5, fp) * 0.7);\nfloat ink = max(arcMarker(r, a, 41.0, 1.85, 1.15, fp),\narcMarker(r, a, 31.0, 1.72, 0.95, fp));\nink = max(ink, smoothstep(1.6 + fp, 0.5, abs(r - 25.5))\n* smoothstep(0.62, 0.54, abs(wrapPi(a - 1.80))));\nc = mix(c, vec3(0.045, 0.040, 0.055), ink);\ngloss = mix(0.62, 0.10, ink);\nreturn c;\n}\nif (d < 2.5){\n// ---- 2: Frutiger Aero. Glass, water, one leaf's worth of green. ----\nvec3 sky = mix(vec3(0.06, 0.55, 0.86), vec3(0.72, 0.93, 1.00),\nsmoothstep(-40.0, 30.0, lp.y));\nvec3 grass = mix(vec3(0.32, 0.74, 0.16), vec3(0.08, 0.42, 0.14),\nsmoothstep(-10.0, -52.0, lp.y));\nc = mix(sky, grass, smoothstep(-4.0, -14.0, lp.y));\n// the glossy sweep across the top, the thing that made it Aero\nc = mix(c, vec3(1.0),\ncrescent(lp, vec2(0.0, 8.0), 56.0, vec2(-4.0, -46.0), 70.0, aa) * 0.42);\nfloat rim;\nfloat bm = bubbles(lp, 3.7, fp, rim);\nc = mix(c, c * 1.35 + vec3(0.10, 0.14, 0.16), bm * 0.55);\nc = mix(c, vec3(1.0), clamp(rim, 0.0, 1.0) * 0.90);\nc = specBand(c, r, a, vec3(0.02, 0.14, 0.22), fp);\nc = mix(c, vec3(1.0), arcText(r, a, 41.5, 3.0, 1.90, 0.62, 3.0, 21.0, fp) * 0.92);\ngloss = 0.88;\nreturn c;\n}\nif (d < 3.5){\n// ---- 3: neon grid. Dark plastic, magenta wireframe, glow. ----\nc = mix(vec3(0.020, 0.024, 0.075), vec3(0.06, 0.02, 0.13),\nsmoothstep(20.0, 58.0, r));\nfloat rings = lineAt(r, 6.5, 0.30, fp);\nfloat spokes = lineAt(a * r, 6.5 * r / max(r, 8.0) * 3.0, 0.34, fp);\nvec3 neon = mix(vec3(0.95, 0.10, 0.62), vec3(0.10, 0.85, 0.98),\nsmoothstep(22.0, 56.0, r));\nc += neon * (rings * 0.85 + spokes * 0.55);\nc += neon * 0.16 * exp(-abs(mod(r, 6.5) - 3.25) * 0.9);\nc = mix(c, vec3(0.95, 0.98, 1.0),\nsmoothstep(1.4 + fp, 0.5, abs(r - 30.0)) * 0.85);\nc = specBand(c, r, a, vec3(0.55, 0.85, 1.0), fp);\nc = mix(c, vec3(0.10, 0.98, 0.85), arcText(r, a, 41.0, 2.9, -2.30, 0.58, 3.0, 33.0, fp) * 0.95);\ngloss = 0.80;\nreturn c;\n}\nif (d < 4.5){\n// ---- 4: flames. Tongues off the hub, tribal and orange. ----\nc = vec3(0.030, 0.028, 0.032) * (0.8 + 0.4 * vnoise(lp * 1.3));\nfloat k = 0.5 + 0.5 * sin(a * 7.0 + 1.35 * sin(a * 3.0 + 0.7));\nfloat k2 = 0.5 + 0.5 * sin(a * 11.0 - 2.1 * sin(a * 2.0));\nfloat top = 24.0 + 30.0 * k;\nfloat top2 = 22.0 + 20.0 * k2;\nfloat f1 = smoothstep(top + fp, top - 8.0, r);\nfloat f2 = smoothstep(top2 + fp, top2 - 5.0, r);\nvec3 hot = mix(vec3(1.00, 0.92, 0.35), vec3(0.94, 0.16, 0.03),\nsmoothstep(20.0, top, r));\nc = mix(c, hot * 0.55, f1);\nc = mix(c, hot, f2);\nc = mix(c, vec3(1.00, 0.96, 0.70), f2 * smoothstep(top2 - 5.0, top2 - 12.0, r) * 0.55);\nc = specBand(c, r, a, vec3(0.95, 0.70, 0.30), fp);\nc = mix(c, vec3(1.0, 0.88, 0.55), arcText(r, a, 44.5, 2.4, 1.35, 0.50, 2.8, 51.0, fp) * 0.9);\ngloss = 0.55;\nreturn c;\n}\nif (d < 5.5){\n// ---- 5: bubblegum. Starburst, dots, a badge. ----\nc = mix(vec3(1.00, 0.36, 0.66), vec3(1.00, 0.76, 0.20),\nsmoothstep(18.0, 58.0, r));\nfloat rays = pow(max(sin(a * 8.0 + 0.4), 0.0), 3.0);\nc = mix(c, vec3(1.00, 0.93, 0.30), rays * 0.55 * smoothstep(20.0, 40.0, r));\nc = mix(c, vec3(1.0), polka(lp, 9.5, 0.20, fp) * 0.80);\nc = mix(c, vec3(0.16, 0.06, 0.30),\nsmoothstep(15.0 + fp, 14.2, abs(length(lp - vec2(0.0, 34.0)))) * 0.0);\nfloat badge = smoothstep(11.0 + fp, 10.2, length(lp - vec2(-4.0, 33.0)));\nc = mix(c, vec3(0.20, 0.07, 0.34), badge);\nc = mix(c, vec3(1.00, 0.93, 0.30), badge\n* smoothstep(8.4, 7.6, length(lp - vec2(-4.0, 33.0))) * 0.0);\nc = specBand(c, r, a, vec3(0.30, 0.05, 0.24), fp);\nc = mix(c, vec3(0.22, 0.04, 0.26), arcText(r, a, 43.0, 2.6, -1.55, 0.55, 2.9, 67.0, fp) * 0.92);\ngloss = 0.70;\nreturn c;\n}\nif (d < 6.5){\n// ---- 6: bootleg. Inkjet stripe over plain stock. ----\nc = vec3(0.84, 0.84, 0.82) * (0.90 + 0.14 * vnoise(lp * 2.1));\nfloat band = smoothstep(13.0 + fp, 11.5, abs(lp.y * 0.94 - lp.x * 0.34 - 6.0));\nvec3 stripe = mix(vec3(0.10, 0.24, 0.72), vec3(0.72, 0.14, 0.34),\nsmoothstep(-40.0, 40.0, lp.x));\nc = mix(c, stripe, band);\nc = mix(c, vec3(1.0), band\n* smoothstep(3.0 + fp, 1.6, abs(lp.y * 0.94 - lp.x * 0.34 - 15.0)) * 0.75);\nc = specBand(c, r, a, vec3(0.10, 0.12, 0.20), fp);\nc = mix(c, vec3(0.06, 0.08, 0.14), arcText(r, a, 41.0, 2.7, 2.55, 0.56, 3.0, 83.0, fp) * 0.9);\nc = mix(c, vec3(0.10, 0.14, 0.26), arcText(r, a, 32.5, 1.1, 2.55, 0.48, 1.5, 91.0, fp) * 0.8);\ngloss = 0.66;\nreturn c;\n}\nif (d < 7.5){\n// ---- 7: camo. Four tones thresholded off two noise fields. ----\nfloat n1 = vnoise(lp * 0.055) * 0.66 + vnoise(lp * 0.135) * 0.34;\nfloat n2 = vnoise(lp * 0.070 + 40.0) * 0.72 + vnoise(lp * 0.17 + 9.0) * 0.28;\nc = vec3(0.31, 0.32, 0.20);\nc = mix(c, vec3(0.50, 0.44, 0.27), smoothstep(0.47, 0.51, n1));\nc = mix(c, vec3(0.15, 0.18, 0.12), smoothstep(0.55, 0.59, n2));\nc = mix(c, vec3(0.07, 0.07, 0.06), smoothstep(0.52, 0.56, n1 * n2 * 1.9));\nc = specBand(c, r, a, vec3(0.86, 0.87, 0.78), fp);\nc = mix(c, vec3(0.90, 0.92, 0.80), arcText(r, a, 42.0, 2.6, 0.65, 0.52, 2.9, 101.0, fp) * 0.9);\ngloss = 0.42;\nreturn c;\n}\nif (d < 8.5){\n// ---- 8: chrome. A gradient with a hard horizon cut through it,\n// which is the whole trick: chrome is a picture of a room. ----\nfloat g1 = 0.5 + 0.5 * sin(lp.y * 0.095 + lp.x * 0.042);\nc = mix(vec3(0.26, 0.28, 0.34), vec3(0.93, 0.95, 1.00), smoothstep(0.20, 0.80, g1));\nfloat hz = lp.y + lp.x * 0.42 + 6.0;\nc = mix(c, vec3(0.05, 0.07, 0.13), smoothstep(2.6 + fp, 0.9, abs(hz)));\nc = mix(c, vec3(0.62, 0.78, 0.98), smoothstep(0.0, -16.0, hz) * 0.45);\nc = mix(c, vec3(1.0),\ncrescent(lp, vec2(-6.0, 6.0), 52.0, vec2(-26.0, -34.0), 58.0, aa) * 0.55);\nc = specBand(c, r, a, vec3(0.05, 0.07, 0.14), fp);\nc = mix(c, vec3(0.04, 0.06, 0.12), arcText(r, a, 42.5, 3.0, -0.40, 0.56, 3.1, 113.0, fp) * 0.92);\ngloss = 0.92;\nreturn c;\n}\nif (d < 9.5){\n// ---- 9: tribal. Pointed tongues off a phase-modulated sine,\n// which is every tattoo flash sheet of 2002. ----\nc = vec3(0.040, 0.038, 0.044);\nfloat k = sin(a * 5.0 + 2.4 * sin(a * 2.0 + r * 0.045));\nfloat t1 = smoothstep(0.94, 0.998, abs(k))\n* smoothstep(23.0, 29.0, r) * smoothstep(58.0, 48.0, r);\nfloat k2 = sin(a * 3.0 - 1.7 * sin(a + r * 0.08) + 1.6);\nfloat t2 = smoothstep(0.965, 0.999, abs(k2)) * smoothstep(25.0, 33.0, r);\nc = mix(c, vec3(0.94, 0.95, 0.93), max(t1, t2 * 0.8));\nc = specBand(c, r, a, vec3(0.80, 0.82, 0.80), fp);\ngloss = 0.60;\nreturn c;\n}\nif (d < 10.5){\n// ---- 10: falling code. Columns of glyphs, brightest at the head. ----\nc = vec3(0.006, 0.026, 0.013);\nfloat cl = floor(lp.x / 4.7);\nfloat ph = hash11(cl * 1.73 + 0.4);\nfloat yy = lp.y / 5.4 + ph * 37.0;\nfloat gl = step(0.30, hash12(vec2(cl, floor(yy))));\nfloat head = fract(yy * 0.075 + ph);\n// the trail dims fast behind the head, which is the only thing\n// that separates a column of code from a field of green blocks\nc += vec3(0.10, 0.90, 0.28) * gl * pow(smoothstep(0.98, 0.10, head), 2.2) * 0.95;\nc += vec3(0.80, 1.00, 0.88) * gl * smoothstep(0.965, 0.995, head) * 1.15;\nc = specBand(c, r, a, vec3(0.30, 1.00, 0.45), fp);\ngloss = 0.78;\nreturn c;\n}\nif (d < 11.5){\n// ---- 11: chequered flag, on a band ----\nc = vec3(0.88, 0.88, 0.86) * (0.92 + 0.12 * vnoise(lp * 2.0));\nfloat band = smoothstep(30.0 - fp, 31.5, r) * smoothstep(46.0 + fp, 44.5, r);\nfloat chk = mod(floor(a * 18.0 / TAU) + floor(r / 7.5), 2.0);\nc = mix(c, mix(vec3(0.06), vec3(0.97), chk), band);\nc = mix(c, vec3(0.86, 0.10, 0.10), smoothstep(1.6 + fp, 0.6, abs(r - 28.0)));\nc = mix(c, vec3(0.86, 0.10, 0.10), smoothstep(1.6 + fp, 0.6, abs(r - 48.5)));\nc = specBand(c, r, a, vec3(0.08, 0.09, 0.12), fp);\nc = mix(c, vec3(0.06, 0.07, 0.10), arcText(r, a, 53.0, 1.9, 2.05, 0.48, 2.6, 127.0, fp) * 0.9);\ngloss = 0.68;\nreturn c;\n}\nif (d < 12.5){\n// ---- 12: splatter. Thresholded noise, which is what a splat is. ----\nc = vec3(0.87, 0.87, 0.85) * (0.92 + 0.12 * vnoise(lp * 2.2));\nfloat g1 = vnoise(lp * 0.115) * 0.68 + vnoise(lp * 0.30) * 0.32;\nc = mix(c, vec3(0.055, 0.05, 0.06), smoothstep(0.60, 0.645, g1));\nfloat g2 = vnoise(lp * 0.16 + 17.0) * 0.70 + vnoise(lp * 0.44 + 3.0) * 0.30;\nc = mix(c, vec3(0.84, 0.11, 0.09), smoothstep(0.665, 0.70, g2));\nfloat g3 = vnoise(lp * 0.62 + 31.0);\nc = mix(c, vec3(0.055, 0.05, 0.06), smoothstep(0.74, 0.80, g3) * 0.7);\nc = specBand(c, r, a, vec3(0.08, 0.08, 0.10), fp);\nc = mix(c, vec3(0.05, 0.05, 0.07), arcText(r, a, 41.0, 3.0, -2.7, 0.54, 3.0, 139.0, fp) * 0.92);\ngloss = 0.52;\nreturn c;\n}\nif (d < 13.5){\n// ---- 13: acid smiley. The face is the annulus -- a CD already\n// has a hole where a nose would go, so it draws itself. ----\nc = vec3(1.00, 0.85, 0.06);\nfloat eye = min(length(lp - vec2(-19.0, 25.0)), length(lp - vec2(19.0, 25.0)));\nc = mix(c, vec3(0.05, 0.04, 0.03), smoothstep(6.6 + fp, 5.0, eye));\nvec2 sc = lp - vec2(0.0, 7.0);\nfloat sl = max(length(sc), 1e-3);\nfloat smile = smoothstep(3.4 + fp, 2.0, abs(sl - 33.0))\n* smoothstep(-0.12, -0.42, sc.y / sl);\nc = mix(c, vec3(0.05, 0.04, 0.03), smile);\nc = specBand(c, r, a, vec3(0.09, 0.07, 0.03), fp);\ngloss = 0.82;\nreturn c;\n}\nif (d < 14.5){\n// ---- 14: acid. A phase-modulated polar sine, which is the\n// cheapest honest way to get a 303 line drawn as a picture. ----\nfloat w = a * 3.0 + r * 0.30 + 2.3 * sin(r * 0.105 + a * 2.0);\nfloat b1 = 0.5 + 0.5 * sin(w);\nfloat b2 = 0.5 + 0.5 * sin(w * 2.0 + 1.1);\nc = mix(vec3(0.03, 0.05, 0.02), vec3(0.72, 1.00, 0.06), smoothstep(0.32, 0.78, b1));\nc = mix(c, vec3(0.95, 0.05, 0.62), smoothstep(0.68, 0.93, b2) * 0.85);\nc = mix(c, vec3(0.02, 0.04, 0.02), smoothstep(0.88, 0.99, b1 * b2));\nc = specBand(c, r, a, vec3(0.85, 1.00, 0.30), fp);\ngloss = 0.86;\nreturn c;\n}\nif (d < 15.5){\n// ---- 15: hazard. Diagonal stripes and a warning ring. ----\nfloat st = mod(floor((lp.x + lp.y) * 0.7071 / 7.5), 2.0);\nc = mix(vec3(0.05, 0.05, 0.04), vec3(1.00, 0.82, 0.03), st);\nc = mix(c, vec3(0.05, 0.05, 0.04), smoothstep(3.0 + fp, 1.6, abs(r - 33.0)));\nc = mix(c, vec3(1.00, 0.82, 0.03), smoothstep(1.3 + fp, 0.4, abs(r - 33.0)));\nc = specBand(c, r, a, vec3(0.06, 0.06, 0.05), fp);\nc = mix(c, vec3(0.05, 0.05, 0.04),\narcText(r, a, 43.0, 2.6, 1.20, 0.52, 2.9, 151.0, fp) * 0.92);\ngloss = 0.62;\nreturn c;\n}\nif (d < 16.5){\n// ---- 16: op-art spiral. Three arms, one turn per 18mm, so it\n// stays coarse enough to read when the disc is small on screen. ----\nfloat sp = fract(a * 3.0 / TAU + r * 0.055);\nc = mix(vec3(0.035, 0.035, 0.040), vec3(0.96, 0.96, 0.94),\nsmoothstep(0.47, 0.53, sp) * smoothstep(0.97, 0.91, sp));\nc = mix(c, vec3(0.90, 0.09, 0.28), smoothstep(1.6 + fp, 0.5, abs(r - 51.0)));\nc = specBand(c, r, a, vec3(0.90, 0.09, 0.28), fp);\ngloss = 0.74;\nreturn c;\n}\nif (d < 17.5){\n// ---- 17: a mixtape. Title, underline, numbered tracks. ----\nc = vec3(0.81, 0.81, 0.78) * (0.90 + 0.16 * vnoise(lp * 2.2));\nc = mix(c, vec3(0.16, 0.18, 0.34), dashRing(r, a, 55.5, 0.7, 220.0, 0.5, fp) * 0.45);\nfloat blue;\nfloat ink = mixtape(lp, fp, blue);\nc = mix(c, vec3(0.05, 0.05, 0.06), ink);\nc = mix(c, vec3(0.10, 0.16, 0.52), blue);\ngloss = mix(0.60, 0.10, max(ink, blue));\nreturn c;\n}\nif (d < 18.5){\n// ---- 18: black metal. An illegible spiky logo, which is the\n// genre's whole typographic project, plus a hairline ring. ----\nc = vec3(0.028, 0.026, 0.030) * (0.80 + 0.40 * vnoise(lp * 1.6));\nfloat spike = abs(sin(a * 17.0 + 1.15 * sin(a * 6.0 + 0.7)));\nfloat top = 31.0 + 21.0 * pow(spike, 3.0);\nfloat lg = smoothstep(top + fp, top - 2.4, r) * smoothstep(26.5, 29.0, r);\nc = mix(c, vec3(0.93, 0.94, 0.92), lg);\nc = mix(c, vec3(0.88, 0.89, 0.88),\nsmoothstep(0.7 + fp, 0.2, abs(r - 55.5))\n+ smoothstep(0.6 + fp, 0.2, abs(r - 24.0)));\nc = specBand(c, r, a, vec3(0.72, 0.73, 0.72), fp);\ngloss = 0.30;\nreturn c;\n}\nif (d < 19.5){\n// ---- 19: punk. Photocopied to death, with ransom-note blocks\n// pasted on at whatever angle they came off the scissors. ----\nc = vec3(0.91, 0.91, 0.89) * (0.84 + 0.30 * vnoise(lp * 3.0));\nfloat grit = vnoise(lp * 0.85) * 0.6 + vnoise(lp * 2.3) * 0.4;\nc = mix(c, vec3(0.055, 0.05, 0.055), smoothstep(0.690, 0.735, grit));\nfor (int i = 0; i < 7; i++){\nfloat fi = float(i);\nvec2 bc = vec2(hash11(fi * 4.1 + 1.0), hash11(fi * 6.3 + 8.0)) * 84.0 - 42.0;\nfloat ba = (hash11(fi * 2.9 + 5.0) - 0.5) * 1.1;\nfloat cs = cos(ba), sn = sin(ba);\nvec2 q = lp - bc;\nq = vec2(q.x * cs + q.y * sn, -q.x * sn + q.y * cs);\nvec2 hs = vec2(mix(5.0, 10.0, hash11(fi * 7.7 + 3.0)), 6.5);\nfloat blk = smoothstep(hs.x + fp, hs.x - 0.7, abs(q.x))\n* smoothstep(hs.y + fp, hs.y - 0.7, abs(q.y));\nfloat inv = step(0.5, hash11(fi * 9.1 + 2.0));\nc = mix(c, mix(vec3(0.055, 0.05, 0.055), vec3(0.95, 0.95, 0.93), inv), blk);\nc = mix(c, mix(vec3(0.95, 0.95, 0.93), vec3(0.055, 0.05, 0.055), inv),\nblk * smoothstep(2.4 + fp, 1.4, abs(q.y)) * 0.95);\n}\n// the circle somebody drew round it by hand\nc = mix(c, vec3(0.055, 0.05, 0.055),\nsmoothstep(2.1 + fp, 0.7, abs(r - 40.0 - 2.2 * sin(a * 3.0 + 1.0))));\nc = specBand(c, r, a, vec3(0.07, 0.06, 0.07), fp);\ngloss = 0.30;\nreturn c;\n}\nif (d < 20.5){\n// ---- 20: hardcore. Two colours, a stencil bar, distressed. ----\nc = vec3(0.055, 0.050, 0.052);\nfloat bar = smoothstep(11.5 + fp, 9.2, abs(lp.y * 0.86 - lp.x * 0.51 + 5.0));\nc = mix(c, vec3(0.82, 0.09, 0.07), bar);\nfloat burst = smoothstep(0.0, -2.5, r - (17.0 + 15.0 * cos(a * 5.0 - 1.2)));\nc = mix(c, vec3(0.95, 0.93, 0.88), burst * 0.92);\n// the stencil never sprays clean\nfloat wear = vnoise(lp * 1.15) * 0.65 + vnoise(lp * 3.1) * 0.35;\nc = mix(c, vec3(0.055, 0.050, 0.052), smoothstep(0.54, 0.63, wear) * 0.7);\nc = specBand(c, r, a, vec3(0.90, 0.88, 0.84), fp);\nc = mix(c, vec3(0.95, 0.93, 0.88),\narcText(r, a, 44.0, 2.8, -0.9, 0.54, 3.0, 163.0, fp) * 0.9);\ngloss = 0.36;\nreturn c;\n}\n// ---- 21: blank stock ----\nc = vec3(0.74, 0.74, 0.71) * (0.88 + 0.18 * vnoise(lp * 2.3));\nc = mix(c, vec3(0.30, 0.32, 0.36), dashRing(r, a, 55.0, 0.7, 210.0, 0.5, fp) * 0.5);\ngloss = 0.58;\nreturn c;\n}\n\nvec3 shadeLabel(vec3 p, vec3 rd, vec3 lp3, float fp){\nvec3 N = vec3(0.0, -1.0, 0.0);\nvec2 lp = p.xz;\nfloat r = length(lp);\nfloat a = atan(lp.y, lp.x);\nvec3 dl = lp3 - p;\nfloat d2 = dot(dl, dl);\nvec3 L = dl * inversesqrt(d2);\nfloat irr = uLampPower * 30000.0 / max(d2, 1.0);\nfloat ndl = max(dot(N, L), 0.0);\n\n// Print starts outside the clamping area and stops short of the\n// edge; inside and outside that is bare polycarbonate.\nfloat ink = smoothstep(R_CLAMP - 1.2, R_CLAMP + 1.4, r)\n* smoothstep(R_OUT - 0.5, R_OUT - 2.4, r);\nfloat gloss;\nvec3 art = labelArt(lp, r, a, fp, gloss);\nvec3 base = mix(vec3(0.030, 0.030, 0.036), art, ink);\nfloat g = mix(0.12, gloss, ink);\n\nreturn base * (ndl * irr + uAmbient * 1.8)\n+ vec3(0.95, 0.95, 1.0) * ggx(L, -rd, N, mix(0.44, 0.16, g)) * ndl * irr * 0.09\n+ studioEnv(gM * reflect(rd, N)) * mix(0.34, 0.05 + 0.20 * g, ink);\n}\n\n// ---- the floppies ----\n// A 3.5 inch disk is 90 x 94 x 3.3 mm with a corner cut off: a\n// box and a half-space, both closed form. That cut corner is the\n// only part of the silhouette that says floppy not coaster.\n//\n// It tumbles on the same law as the discs, which is not a\n// shortcut. The perpendicular axis theorem gives a flat plate\n// I3 = I1 + I2, so a SQUARE plate has I3 = 2*I1 exactly -- the\n// same ratio as a disc, hence the same psi = -phi*cos(theta)/2.\n// A floppy is 90 by 94, within four percent of square.\n#define F_W 45.0    // half width\n#define F_T 1.65    // half thickness\n#define F_H 47.0    // half height\n\n// a filled rectangle, 1 inside, antialiased by the footprint\nfloat bx2(vec2 p, vec2 b, float fp){\nvec2 d = abs(p) - b;\nreturn smoothstep(fp, -fp, max(d.x, d.y));\n}\n\nvec3 shellColour(float k){\nif (k < 0.5) return vec3(0.042, 0.042, 0.047);   // black, the default\nif (k < 1.5) return vec3(0.60, 0.575, 0.50);     // ivory, the older ones\nif (k < 2.5) return vec3(0.075, 0.19, 0.52);\nif (k < 3.5) return vec3(0.50, 0.075, 0.085);\nif (k < 4.5) return vec3(0.70, 0.58, 0.055);\nif (k < 5.5) return vec3(0.075, 0.40, 0.19);\nreturn vec3(0.19, 0.20, 0.235);                  // smoke\n}\n\n// A row of block letters. Period lettering, not anybody's\n// trademark: every mark on these labels is a style of the era, not\n// a copy of a real logo, which is not a thing to publish into the\n// public domain on someone else's behalf.\nfloat wordBar(vec2 q, vec2 ctr, vec2 hf, float pitch, float seed, float fp){\nvec2 d = q - ctr;\nfloat gi = floor((d.x + hf.x) / pitch);\nfloat g  = fract((d.x + hf.x) / pitch);\nfloat hh = hf.y * mix(0.55, 1.0, hash11(gi * 1.7 + seed));\n// `half` is a reserved word in GLSL ES and this was named that\nreturn smoothstep(fp, -fp, abs(d.x) - hf.x)\n* smoothstep(0.70, 0.55, g) * smoothstep(0.05, 0.20, g)\n* smoothstep(hh + fp, hh - 0.3, abs(d.y));\n}\n\n// The paper label. Nine of them, because a box of floppies never\n// had two the same.\nvec3 floppyLabel(vec2 q, float fp, out float ink){\nink = 0.0;\nvec3 c = vec3(0.88, 0.87, 0.83) * (0.93 + 0.10 * vnoise(q * 2.4));\nfloat v = gLabel;\nif (v < 1.5){\n// 0/1: blank, or written on\nif (v > 0.5){\nfloat blue;\nfloat m = mixtape(q * 1.55, fp * 1.55, blue);\nc = mix(c, vec3(0.06, 0.06, 0.07), m);\nc = mix(c, vec3(0.10, 0.16, 0.52), blue);\nink = max(m, blue);\n}\nreturn c;\n}\nif (v < 2.5){\n// 2: a printed brand band across the top, with microtext\nc = mix(c, vec3(0.09, 0.24, 0.62), bx2(q - vec2(0.0, 21.0), vec2(33.0, 7.0), fp));\nc = mix(c, vec3(0.93, 0.94, 0.96),\nbx2(q - vec2(-8.0, 21.0), vec2(15.0, 2.0), fp)\n* smoothstep(0.55, 0.40, fract(q.x * 0.62)));\nfor (int i = 0; i < 4; i++){\nfloat fy = 8.0 - float(i) * 8.0;\nc = mix(c, vec3(0.35, 0.36, 0.40),\nbx2(q - vec2(-4.0, fy), vec2(26.0, 0.7), fp)\n* smoothstep(0.60, 0.42, fract(q.x * 0.9 + float(i))));\n}\nreturn c;\n}\nif (v < 3.5){\n// 3: colour coded -- a big dot and a couple of lines\nc = mix(c, vec3(0.86, 0.30, 0.06),\nsmoothstep(fp, -fp, length(q - vec2(-20.0, 16.0)) - 8.0));\nfloat blue;\nfloat m = mixtape(q * 1.9 + vec2(14.0, -8.0), fp * 1.9, blue);\nc = mix(c, vec3(0.07, 0.07, 0.08), m);\nink = m;\nreturn c;\n}\nif (v < 4.5){\n// 4: the six-band stripe. Nothing said 1984 louder.\nfloat band = bx2(q - vec2(0.0, 14.0), vec2(33.0, 10.8), fp);\nfloat bi = clamp(floor((24.8 - q.y) / 3.6), 0.0, 5.0);\nvec3 bc = (bi < 0.5) ? vec3(0.36, 0.70, 0.24)\n: (bi < 1.5) ? vec3(0.97, 0.81, 0.06)\n: (bi < 2.5) ? vec3(0.94, 0.53, 0.05)\n: (bi < 3.5) ? vec3(0.85, 0.10, 0.12)\n: (bi < 4.5) ? vec3(0.49, 0.14, 0.55)\n: vec3(0.10, 0.47, 0.79);\nc = mix(c, bc, band);\nc = mix(c, vec3(0.10, 0.11, 0.14),\nwordBar(q, vec2(-4.0, -6.0), vec2(24.0, 4.2), 3.4, 7.0, fp));\nc = mix(c, vec3(0.35, 0.36, 0.40),\nwordBar(q, vec2(-10.0, -17.0), vec2(17.0, 1.5), 1.9, 21.0, fp));\nreturn c;\n}\nif (v < 5.5){\n// 5: a board. Solder mask, gold traces, pads, a hex badge.\nc = vec3(0.055, 0.29, 0.185);\nfloat tr = max(lineAt(q.y - 4.0, 9.0, 0.55, fp),\nlineAt(q.x + 3.0, 11.0, 0.55, fp) * step(q.y, 6.0));\nc = mix(c, vec3(0.82, 0.66, 0.20), tr * 0.95);\nvec2 pd = mod(q + vec2(1.5, 0.5), vec2(11.0, 9.0)) - vec2(5.5, 4.5);\nc = mix(c, vec3(0.86, 0.70, 0.24),\nsmoothstep(fp, -fp, length(pd) - 1.6));\nc = mix(c, vec3(0.055, 0.29, 0.185),\nsmoothstep(fp, -fp, length(pd) - 0.7));\n// the hex badge every board silkscreen had\nvec2 hq = abs(q - vec2(19.0, 17.0));\nfloat hex = max(hq.x * 0.866 + hq.y * 0.5, hq.y) - 7.0;\nc = mix(c, vec3(0.90, 0.92, 0.88), smoothstep(fp, -fp, hex)\n* smoothstep(-1.6, -2.2, hex));\nc = mix(c, vec3(0.88, 0.90, 0.86),\nwordBar(q, vec2(-9.0, -20.0), vec2(20.0, 2.6), 2.4, 33.0, fp));\nreturn c;\n}\nif (v < 6.5){\n// 6: graph paper, written on\nc = mix(c, vec3(0.60, 0.71, 0.86),\nmax(lineAt(q.x, 4.2, 0.16, fp), lineAt(q.y, 4.2, 0.16, fp)) * 0.75);\nc = mix(c, vec3(0.30, 0.44, 0.70),\nmax(lineAt(q.x, 21.0, 0.26, fp), lineAt(q.y, 21.0, 0.26, fp)) * 0.7);\nfloat blue;\nfloat m = mixtape(q * 1.55 + vec2(0.0, 6.0), fp * 1.55, blue);\nc = mix(c, vec3(0.06, 0.06, 0.07), m);\nc = mix(c, vec3(0.10, 0.16, 0.52), blue);\nink = max(m, blue);\nreturn c;\n}\nif (v < 7.5){\n// 7: corporate speed stripes and a bevelled wordmark\nfloat st = q.y * 0.94 - q.x * 0.34;\nc = mix(c, vec3(0.07, 0.21, 0.58), smoothstep(4.2 + fp, 2.6, abs(st - 15.0)));\nc = mix(c, vec3(0.80, 0.11, 0.10), smoothstep(2.7 + fp, 1.5, abs(st - 8.5)));\nc = mix(c, vec3(0.07, 0.21, 0.58), smoothstep(1.5 + fp, 0.7, abs(st - 3.5)));\nc = mix(c, vec3(0.13, 0.14, 0.18),\nwordBar(q, vec2(-6.0, -8.0), vec2(25.0, 4.6), 3.6, 51.0, fp));\nc = mix(c, vec3(0.72, 0.76, 0.84),\nwordBar(q, vec2(-6.0, -6.6), vec2(25.0, 3.0), 3.6, 51.0, fp) * 0.8);\nc = mix(c, vec3(0.40, 0.42, 0.48),\nwordBar(q, vec2(-13.0, -19.0), vec2(16.0, 1.4), 1.8, 67.0, fp));\nreturn c;\n}\n// 8: no label at all, bare shell\nink = -1.0;\nreturn c;\n}\n\n// n is the outward face normal in the floppy's own frame.\nvec3 shadeFloppy(vec3 p, vec3 n, vec3 rd, vec3 lp3, float fp){\nvec3 shell = shellColour(gDye);\nvec3 c = shell * (0.92 + 0.16 * vnoise(p.xz * 1.9));   // moulding texture\nfloat rough = 0.42;\n\nif (abs(n.y) > 0.5){\nvec2 q = vec2(p.x, p.z) * vec2(sign(n.y), 1.0);   // mirror the back\n// the shutter, on both faces, with its window\nfloat sh = bx2(q - vec2(0.0, 36.0), vec2(21.0, 10.5), fp);\n// the two moulded corner holes\nfloat holes = bx2(vec2(abs(q.x) - 38.0, q.y + 41.5), vec2(3.4, 3.4), fp);\n\nif (n.y > 0.0){\n// ---- label side ----\n// Nobody ever stuck one on straight, and paper sits proud of the\n// shell, so it casts a hairline down two of its sides.\nfloat la = (hash11(gDye * 3.3 + gLabel * 1.9 + 2.0) - 0.5) * 0.10;\nvec2 lq = q - vec2(0.0, -12.0);\nlq = vec2(lq.x * cos(la) + lq.y * sin(la), lq.y * cos(la) - lq.x * sin(la));\nfloat lb = bx2(lq, vec2(32.0, 28.0), fp);\nfloat ink;\nvec3 art = floppyLabel(lq, fp, ink);\nif (ink >= 0.0){\nc = mix(c, c * 0.45, max(bx2(lq + vec2(1.0, 1.0), vec2(32.6, 28.6), fp) - lb, 0.0));\nc = mix(c, art, lb);\nrough = mix(rough, 0.62, lb * (1.0 - max(ink, 0.0) * 0.7));\n}\n// the moulded insertion arrow, up in the corner\nvec2 aq = q - vec2(-35.0, 27.0);\nfloat arw = smoothstep(fp, -fp, max(abs(aq.x) * 1.7 + aq.y - 3.0, -aq.y - 3.0));\nc = mix(c, c * 1.35 + 0.012, arw);\n} else {\n// ---- hub side ----\nfloat hub = smoothstep(fp, -fp, length(q) - 12.5);\nc = mix(c, vec3(0.55, 0.56, 0.59) * (0.85 + 0.3 * vnoise(q * 3.0)), hub);\nc = mix(c, vec3(0.03), bx2(q - vec2(6.5, 0.0), vec2(2.3, 5.2), fp));\nrough = mix(rough, 0.22, hub);\n// the moulded ring the liner is welded round, and the two\n// stiffening ribs every shell has on the back\nc = mix(c, c * 0.80, smoothstep(0.55 + fp, 0.12, abs(length(q) - 21.0)));\nc = mix(c, c * 0.88, smoothstep(0.45 + fp, 0.10, abs(abs(q.x) - 33.0))\n* smoothstep(-40.0, -34.0, q.y));\n}\n// the shutter goes over everything, with the folded lip along its\n// bottom edge and the notch the drive spring sits in\nvec3 steel = (gDye > 5.5 || gDye < 0.5) ? vec3(0.10, 0.10, 0.11)\n: vec3(0.60, 0.615, 0.645);\nc = mix(c, steel * (0.88 + 0.24 * vnoise(vec2(q.x * 6.0, q.y * 0.7))), sh);\nc = mix(c, steel * 1.45, sh * smoothstep(0.7 + fp, 0.15, abs(q.y - 25.7)));\nc = mix(c, steel * 0.55, sh * bx2(q - vec2(18.0, 41.0), vec2(2.6, 3.2), fp));\nc = mix(c, vec3(0.02), bx2(q - vec2(-6.0, 36.0), vec2(9.5, 7.0), fp) * sh);\nrough = mix(rough, 0.16, sh);\nc = mix(c, vec3(0.015), holes);\n// and one corner hole usually has its slider still in it\nif (hash11(gDye * 7.1 + gLabel * 2.3 + 13.0) > 0.45)\nc = mix(c, shell * 1.9 + 0.02,\nbx2(vec2(q.x + 38.0, q.y + 41.5), vec2(3.0, 3.0), fp));\n} else {\n// ---- the edges: a seam runs round the middle ----\nc *= 0.86;\nc = mix(c, shell * 0.45, smoothstep(0.35 + fp, 0.1, abs(p.y)));\n}\n\nvec3 dl = lp3 - p;\nfloat d2 = dot(dl, dl);\nvec3 L = dl * inversesqrt(d2);\nfloat irr = uLampPower * 30000.0 / max(d2, 1.0);\nfloat ndl = max(dot(n, L), 0.0);\nfloat fres = 0.04 + 0.96 * pow(1.0 - max(dot(n, -rd), 0.0), 5.0);\nreturn c * (ndl * irr * 0.34 + uAmbient * 0.75)\n+ vec3(0.96, 0.96, 1.0) * ggx(L, -rd, n, rough) * ndl * irr * 0.085\n+ studioEnv(gM * reflect(rd, n)) * (0.055 + 0.40 * fres) * mix(0.8, 1.7, 1.0 - rough);\n}\n\n// ---- the 1.2mm edge ----\nvec3 shadeRim(vec3 p, vec3 rd, vec3 lp){\nvec3 N = normalize(vec3(p.x, 0.0, p.z));\nvec3 dl = lp - p;\nfloat d2 = dot(dl, dl);\nvec3 L = dl * inversesqrt(d2);\nfloat irr = uLampPower * 30000.0 / max(d2, 1.0);\nfloat ndl = max(dot(N, L), 0.0);\nfloat fres = 0.05 + 0.95 * pow(1.0 - max(dot(N, -rd), 0.0), 5.0);\nreturn studioEnv(gM * reflect(rd, N)) * (0.35 + 1.5 * fres)\n+ vec3(1.0, 0.99, 0.96) * ggx(L, -rd, N, 0.16) * ndl * irr * fres * 1.10\n+ vec3(0.50, 0.58, 0.72) * ndl * irr * 0.045;\n}\n\n// ---- one object in the rain ----\n// What varies between objects is a hash of the index; what varies\n// with time is a whole number of cycles per minute, so the shower\n// still closes its loop. It is recomputed per object per pixel,\n// which is wasteful -- the transforms depend only on the clock --\n// but a fragment shader has nowhere to cache them.\nvoid discAt(float fi, float t, out vec3 pos, out mat3 M,\nout float lab, out float dye, out float burn, out float kind){\n// Placement is a low-discrepancy sequence, not a hash: multiples\n// of an irrational, about as evenly spread as a sequence gets and\n// evenly for every prefix, so turning OBJECTS down thins the\n// shower rather than tearing a hole in it. The constants also stop\n// anything INTERSECTING -- everything falls at one speed, so a\n// close pair stays close for the whole loop, and the obvious\n// triple resonated at stride 7 and put fifteen pairs through each\n// other. These leave 182mm between the nearest two.\nfloat h0 = fract(fi * 0.4142135624 + 0.13);   // column\nfloat h1 = fract(fi * 0.6823278038 + 0.41);   // depth\nfloat h2 = fract(fi * 0.3819660113 + 0.27);   // where in its fall\nfloat h4 = fract(fi * 0.7320508076 + 0.62);   // dye, and where its burn began\nfloat h5 = fract(fi * 0.7548776662 + 0.88);   // precession count\n\nfloat fall = fract(h2 + t * uFall / 60.0);\npos = vec3((h0 - 0.5) * uSpread,\nmix(uSpread * 1.25, -uSpread * 1.25, fall),\n(h1 - 0.5) * uSpread * 0.80 - uSpread * 0.12);\n\n// Each disc gets its own precession count, so they are never in\n// step, and each solves its own nutation angle back out of an\n// integer body count the way the single disc did.\nfloat P = max(floor(uPrecess + 0.5) + floor(h5 * 6.0), 1.0);\nfloat S = floor(P * cos(radians(uWobble)) * 0.5 + 0.5);\nfloat thN = acos(clamp(2.0 * S / P, 0.0, 1.0));\nfloat phi =  TAU * (P * t / 60.0 + h0 * 3.13);\nfloat psi = -TAU * (S * t / 60.0 + h1 * 4.71);\n\nvec3 n = vec3(sin(thN) * cos(phi), cos(thN), sin(thN) * sin(phi));\nvec3 nodeL = cross(vec3(0.0, 1.0, 0.0), n);\nfloat nl = length(nodeL);\nnodeL = (nl > 1e-4) ? nodeL / nl : vec3(1.0, 0.0, 0.0);\nvec3 bx = nodeL * cos(psi) + cross(n, nodeL) * sin(psi);\nM = mat3(bx, n, cross(n, bx));\n\nlab  = mod(fi * 9.0 + 3.0, 22.0);\ndye  = (float(uDye) > 3.5) ? mod(fi * 3.0 + 1.0, 4.0) : float(uDye);\nburn = fract(uBurn + h4 * 0.83);\n// A floppy re-uses lab and dye for its own label and shell, which\n// saves threading two more values through everything.\nkind = (fract(fi * 0.7182818285 + 0.77) < uFloppy) ? 1.0 : 0.0;\nif (kind > 0.5){\nlab = mod(fi * 4.0 + 1.0, 9.0);\ndye = mod(fi * 3.0 + 2.0, 7.0);\n}\n}\n\nvec3 tonemap(vec3 x){\nreturn clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);\n}\n\nvoid main(){\nvec2 fragCoord = gl_FragCoord.xy;\nvec2 R  = RENDERSIZE.xy;\nvec2 uv = (fragCoord - 0.5 * R) / R.y;\nfloat t = TIME * uSpeed;\nvec3 lpw = lampPos(t);\n\n// ---- camera, in world ----\nfloat pit = radians(uCamPitch);\nvec3 ro = vec3(sin(uCamYaw) * cos(pit), sin(pit), cos(uCamYaw) * cos(pit)) * uCamDist;\nvec3 ww = normalize(vec3(0.0, 0.0, 0.0) - ro);\nvec3 uu = normalize(cross(ww, vec3(0.0, 1.0, 0.0)));\nvec3 vv = cross(uu, ww);\nfloat f = 0.5 / tan(radians(uFov) * 0.5);   // uv is +-0.5, not +-1\nvec3 rd  = normalize(uv.x * uu + uv.y * vv + f * ww);\nvec3 rdx = normalize((uv.x + 1.0 / R.y) * uu + uv.y * vv + f * ww);\nvec3 rdy = normalize(uv.x * uu + (uv.y + 1.0 / R.y) * vv + f * ww);\n\n// ---- nearest disc, over three surfaces each ----\nfloat bestT = 1e9, kind = 0.0;\nmat3 bM = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);\nvec3 bPos = vec3(0.0);\nfloat bLab = 0.0, bDye = 0.0, bBurn = 0.0, bKind = 0.0;\n\nfor (int i = 0; i < NDISC; i++){\nfloat fi = float(i);\nif (fi > uCount - 0.5) break;\nvec3 pos;\nmat3 M;\nfloat lab, dye, burn, knd;\ndiscAt(fi, t, pos, M, lab, dye, burn, knd);\n\nvec3 roD = (ro - pos) * M;       // orthonormal, so the transpose\nvec3 rdD = rd * M;\n\nif (knd > 0.5){\n// ---- a floppy: a box, clipped by the cut corner ----\nvec3 iv = 1.0 / (rdD + vec3(1e-7));\nvec3 bh = vec3(F_W, F_T, F_H);\nvec3 ta = (-bh - roD) * iv, tb = (bh - roD) * iv;\nvec3 tsm = min(ta, tb), tbg = max(ta, tb);\nfloat tin = max(max(tsm.x, tsm.y), tsm.z);\nfloat tout = min(min(tbg.x, tbg.y), tbg.z);\n// the chamfer, as a half-space on the diagonal\nvec2 nc = vec2(0.7071, 0.7071);\nfloat kc = 0.7071 * (F_W + F_H) - 5.0;\nfloat dn = dot(rdD.xz, nc), dp = kc - dot(roD.xz, nc);\nif (abs(dn) > 1e-6){\nfloat tc = dp / dn;\nif (dn > 0.0) tout = min(tout, tc); else tin = max(tin, tc);\n} else if (dp < 0.0) { tout = -1.0; }\nif (tout > max(tin, 0.0) && tin < bestT){\nbestT = tin; kind = 4.0; bM = M; bPos = pos;\nbLab = lab; bDye = dye; bBurn = burn; bKind = 1.0;\n}\n} else {\nif (rdD.y < -1e-5){              // the read face\nfloat tt = (TH * 0.5 - roD.y) / rdD.y;\nfloat rT = length((roD + rdD * tt).xz);\nif (tt > 0.0 && tt < bestT && rT > R_HOLE && rT < R_OUT){\nbestT = tt; kind = 1.0; bM = M; bPos = pos;\nbLab = lab; bDye = dye; bBurn = burn; bKind = 0.0;\n}\n}\nif (rdD.y > 1e-5){               // the printed face\nfloat tt = (-TH * 0.5 - roD.y) / rdD.y;\nfloat rT = length((roD + rdD * tt).xz);\nif (tt > 0.0 && tt < bestT && rT > R_HOLE && rT < R_OUT){\nbestT = tt; kind = 2.0; bM = M; bPos = pos;\nbLab = lab; bDye = dye; bBurn = burn; bKind = 0.0;\n}\n}\n{                                // the 1.2mm edge\nvec2 o2 = roD.xz, d2 = rdD.xz;\nfloat qa = dot(d2, d2), qb = dot(o2, d2), qc = dot(o2, o2) - R_OUT * R_OUT;\nfloat h = qb * qb - qa * qc;\nif (h > 0.0){\nfloat tt = (-qb - sqrt(h)) / qa;\nfloat y = roD.y + tt * rdD.y;\nif (tt > 0.0 && tt < bestT && abs(y) < TH * 0.5){\nbestT = tt; kind = 3.0; bM = M; bPos = pos;\nbLab = lab; bDye = dye; bBurn = burn; bKind = 0.0;\n}\n}\n}\n}\n}\n\nvec3 col, glow = vec3(0.0);\nif (kind < 0.5){\ncol = vec3(0.0);\n} else {\ngM = bM; gLabel = bLab; gDye = bDye; gBurn = bBurn; gKind = bKind;\nvec3 roD  = (ro - bPos) * bM;\nvec3 rdD  = rd  * bM;\nvec3 rdxD = rdx * bM;\nvec3 rdyD = rdy * bM;\nvec3 lpD  = lpw * bM;      // rigged per disc, not to the room\nif (bKind > 0.5){\n// recover which face of the box was hit from the slab that won\nvec3 pb = roD + rdD * bestT;\nvec3 an = abs(pb) / vec3(F_W, F_T, F_H);\nvec3 nb = -sign(rdD) * step(an.yzx, an.xyz) * step(an.zxy, an.xyz);\nif (dot(pb.xz, vec2(0.7071)) > 0.7071 * (F_W + F_H) - 5.2)\nnb = vec3(0.7071, 0.0, 0.7071);\ncol = shadeFloppy(pb, normalize(nb), rdD, lpD,\nlength((roD + rdxD * bestT).xz - pb.xz));\n} else if (kind < 1.5){\ncol = shadeDisc(roD, rdD, rdxD, rdyD, lpD, t, glow);\n} else if (kind < 2.5){\nvec3 pb  = roD + rdD * bestT;\nvec3 pbx = roD + rdxD * ((-TH * 0.5 - roD.y) / rdxD.y);\ncol = shadeLabel(pb, rdD, lpD, length(pbx.xz - pb.xz));\n} else {\ncol = shadeRim(roD + rdD * bestT, rdD, lpD);\n}\n}\n\ncol += glow * uBloom;\ncol *= uExposure;\ncol = tonemap(col);\ncol = pow(max(col, 0.0), vec3(0.4545));\ncol *= 1.0 - uVignette * 0.65 * dot(uv, uv);\ncol += (hash12(fragCoord + fract(t) * 71.0) - 0.5) * 0.05;   // fixed film grain\n\ngl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}\n",
    "generator": {
      "model": "Claude-Opus-5",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}