@dopaminefx/effect-checkmate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GLSL ES 3.00 source for **Checkmate** — an unapologetically fabulous winning
3
+ * move. A chess QUEEN pops into place with an overshoot bounce and the whole
4
+ * frame ERUPTS in LGBTQ+ pride: an expanding rainbow swoosh-shockwave, a
5
+ * spinning pride sunburst, and a mob of twinkling 4-point sparkle bling.
6
+ *
7
+ * Authored ONCE here; the toolchain transpiles the MSL + Kotlin variants from
8
+ * this single GLSL (`x-build.shader`). The chess queen is drawn ANALYTICALLY
9
+ * from 2D SDF primitives (a flared trapezoid body + base, a collar bar and five
10
+ * crown balls on stems) so it is byte-identical on every backend — no baked SDF,
11
+ * no sampler, no per-platform fallback.
12
+ *
13
+ * Layers, summed as light (canvas is black, `mix-blend-mode: screen`, so black
14
+ * == no change, bright == cast light onto the page beneath):
15
+ * 1. SUNBURST — radial pride rays spinning behind the queen (uSpin/uRays).
16
+ * 2. SWOOSH — an expanding rainbow shockwave ring whose hue cycles with
17
+ * angle; it bursts outward over life (the "swoosh").
18
+ * 3. QUEEN — the chess piece, filled with a vertical rainbow gradient +
19
+ * a hot white edge, popped in by uPop (overshoot bounce).
20
+ * 4. BLING — a scatter of twinkling 4-point star glints riding outward
21
+ * with the swoosh (the sparkle "bling"), tinted by the palette.
22
+ * 5. FLASH — a hot radial flash at the instant of the pop.
23
+ *
24
+ * whimsy == uStyle: 0 = smooth photoreal spectral glow; 1 = cel POP-ART — the
25
+ * rainbow posterizes into the canonical 6-stripe pride flag and the bling reads
26
+ * as chunky comic stars. The pass runner already snaps the clock "on twos".
27
+ */
28
+ /** Scatter count for the sparkle bling. Single source of truth for the loop cap. */
29
+ export declare const MAX_SPARKLES = 16;
30
+ export declare const CHECKMATE_VERTEX_SRC = "#version 300 es\nvoid main() {\n // Single full-screen triangle from gl_VertexID \u2014 no vertex buffers needed.\n vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);\n}";
31
+ export declare const CHECKMATE_FRAGMENT_SRC = "#version 300 es\nprecision highp float;\nout vec4 fragColor;\n\nuniform vec2 uResolution; // device pixels\nuniform vec2 uOrigin; // queen anchor, gl coords (y up)\nuniform float uAmp; // held-breath envelope amplitude (brightness gate)\nuniform float uPop; // easeOutBack pop scale (overshoot bounce -> 1)\nuniform float uLife; // whole-effect progress 0..1\nuniform float uTimeS; // elapsed seconds (snapped \"on twos\" by style)\nuniform float uExposure; // overall light gain (intensity)\nuniform float uBling; // sparkle density/brightness (intensity)\nuniform float uSwoosh; // rainbow shockwave reach (intensity)\nuniform float uRays; // pride sunburst ray count (integer)\nuniform float uSpin; // sunburst/swoosh rotation speed\nuniform float uSizeFrac; // queen box size as a fraction of min viewport dim\nuniform float uSeed; // per-fire scatter/hue offset\nuniform float uStyle; // 0..1 photoreal spectral -> cel pop-art pride flag\nuniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)\nuniform vec2 uShadowOffset; // device-px offset of the cast silhouette\nuniform float uShadowSoft; // penumbra softness in device px (blur tap radius)\nuniform float uShadowStrength;// 0..1 max darkening of the multiply layer\nuniform vec3 uC0; // accent palette (sparkle tint) \u2014 per fire\nuniform vec3 uC1; // mid\nuniform vec3 uC2; // outer accent\n\n#define MAX_SPARKLES 16\n\n#define TAU 6.28318530718\n\n\nfloat hash11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }\nvec2 hash21(float p){\n vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.xx + p3.yz) * p3.zy);\n}\n\n\nvec3 paletteMix(float t){\n t = clamp(t, 0.0, 1.0);\n return t < 0.5 ? mix(uC0, uC1, t * 2.0) : mix(uC1, uC2, (t - 0.5) * 2.0);\n}\n\n\nfloat sdSeg(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-3), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\n\nvec3 tonemapACES(vec3 x){\n const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n\n\nvec3 ditherAdd(vec3 col, vec2 frag, float t, float fade){\n float dz = hash11(dot(frag, vec2(12.989, 78.233)) + t) - 0.5;\n return col + (dz / 255.0) * fade;\n}\n\n\n// ---- The pride spectrum -----------------------------------------------------\n// Smooth IQ-cosine rainbow: a continuous, saturated spectral sweep over [0,1).\nvec3 prideSmooth(float t){\n t = fract(t);\n return 0.5 + 0.5 * cos(TAU * (t + vec3(0.0, 0.33, 0.67)));\n}\n// The canonical 6-stripe pride FLAG, posterized from t (red\u2192violet).\nvec3 prideFlag(float t){\n t = fract(t);\n if (t < 0.16667) return vec3(0.94, 0.10, 0.12); // red\n if (t < 0.33333) return vec3(1.00, 0.55, 0.06); // orange\n if (t < 0.50000) return vec3(1.00, 0.93, 0.10); // yellow\n if (t < 0.66667) return vec3(0.18, 0.70, 0.22); // green\n if (t < 0.83333) return vec3(0.10, 0.36, 0.90); // blue\n return vec3(0.46, 0.12, 0.62); // violet\n}\n// Blend smooth\u2194flag by whimsy (style): cel end snaps to the 6 flag stripes.\nvec3 prideColor(float t, float style){\n return mix(prideSmooth(t), prideFlag(t), style);\n}\n\n// ---- 2D SDF primitives for the chess QUEEN silhouette -----------------------\nfloat sdBox(vec2 p, vec2 b){\n vec2 d = abs(p) - b;\n return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);\n}\n// Inigo Quilez trapezoid SDF: half-widths r1 (bottom) and r2 (top), half-height he.\nfloat sdTrapezoid(vec2 p, float r1, float r2, float he){\n vec2 k1 = vec2(r2, he);\n vec2 k2 = vec2(r2 - r1, 2.0 * he);\n p.x = abs(p.x);\n vec2 ca = vec2(p.x - min(p.x, (p.y < 0.0) ? r1 : r2), abs(p.y) - he);\n vec2 cb = p - k1 + k2 * clamp(dot(k1 - p, k2) / dot(k2, k2), 0.0, 1.0);\n float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;\n return s * sqrt(min(dot(ca, ca), dot(cb, cb)));\n}\n// Signed distance to the queen, in LOCAL units (q centered, y up, ~[-1,1]).\n// Union (min) of: base foot, flared body, collar band, five crown balls + stems.\nfloat queenDist(vec2 q){\n float d = sdTrapezoid(q - vec2(0.0, -0.74), 0.60, 0.40, 0.12); // base foot\n d = min(d, sdTrapezoid(q - vec2(0.0, -0.10), 0.46, 0.15, 0.50)); // flared body\n d = min(d, sdBox(q - vec2(0.0, 0.40), vec2(0.32, 0.05)) - 0.02); // collar band\n // crown balls (center tallest) + the stems joining them to the band\n d = min(d, length(q - vec2(-0.46, 0.55)) - 0.115);\n d = min(d, length(q - vec2(-0.23, 0.62)) - 0.125);\n d = min(d, length(q - vec2( 0.00, 0.71)) - 0.145);\n d = min(d, length(q - vec2( 0.23, 0.62)) - 0.125);\n d = min(d, length(q - vec2( 0.46, 0.55)) - 0.115);\n d = min(d, sdSeg(q, vec2(-0.46, 0.55), vec2(-0.28, 0.42)) - 0.045);\n d = min(d, sdSeg(q, vec2(-0.23, 0.62), vec2(-0.14, 0.42)) - 0.045);\n d = min(d, sdSeg(q, vec2( 0.00, 0.71), vec2( 0.00, 0.42)) - 0.055);\n d = min(d, sdSeg(q, vec2( 0.23, 0.62), vec2( 0.14, 0.42)) - 0.045);\n d = min(d, sdSeg(q, vec2( 0.46, 0.55), vec2( 0.28, 0.42)) - 0.045);\n return d;\n}\n\n// A twinkling 4-point star glint: hot core + two soft anisotropic spikes.\nfloat starGlint(vec2 p, vec2 c, float size){\n vec2 d = (p - c) / max(size, 1e-3);\n float r = length(d);\n float core = exp(-r * r * 5.0);\n float sx = exp(-abs(d.x) * 6.0) * exp(-abs(d.y) * 1.4);\n float sy = exp(-abs(d.y) * 6.0) * exp(-abs(d.x) * 1.4);\n return core + (sx + sy) * 0.7;\n}\n\n// ---- Queen coverage (0..1 fill) at a fragment, with the pop scale applied ----\nfloat queenFill(vec2 frag){\n float R = min(uResolution.x, uResolution.y) * uSizeFrac;\n float scale = mix(0.34, 1.0, clamp(uPop, 0.0, 1.4)); // bounce-in scale\n vec2 q = (frag - uOrigin) / max(R, 1e-3) / max(scale, 1e-3);\n float d = queenDist(q);\n float aa = 1.6 / max(R, 1e-3); // ~1.6 device px, local units\n return smoothstep(aa, -aa, d);\n}\n\n// ---- SHADOW silhouette \u2014 the queen casts a soft offset occlusion. -----------\nfloat occlusion(vec2 frag){\n return clamp(queenFill(frag) * uAmp, 0.0, 1.0);\n}\nvec4 shadowColor(vec2 frag){\n vec2 sp = frag - uShadowOffset;\n float s = uShadowSoft;\n float occ = occlusion(sp);\n occ += occlusion(sp + vec2(s, 0.0));\n occ += occlusion(sp + vec2(-s, 0.0));\n occ += occlusion(sp + vec2(0.0, s));\n occ += occlusion(sp + vec2(0.0, -s));\n occ /= 5.0;\n float dark = clamp(occ, 0.0, 1.0) * uShadowStrength;\n // A faintly warm, regal shadow tint (not a flat grey).\n vec3 tint = mix(vec3(1.0), vec3(0.74, 0.70, 0.78), 1.0);\n vec3 mul = mix(vec3(1.0), tint, dark);\n return vec4(mul, 1.0);\n}\n\nvoid main(){\n vec2 frag = gl_FragCoord.xy;\n float minDim = min(uResolution.x, uResolution.y);\n\n if (uShadow > 0.5) { fragColor = shadowColor(frag); return; }\n\n vec2 rel = frag - uOrigin;\n float r = length(rel);\n float rn = r / minDim; // normalized radius\n float theta = atan(rel.y, rel.x); // -PI..PI\n float gain = uAmp * uExposure;\n float style = uStyle;\n\n vec3 col = vec3(0.0);\n\n // ---- 1. PRIDE SUNBURST: spinning radial rays behind the queen. ----\n float rayN = max(uRays, 1.0);\n float rays = 0.5 + 0.5 * cos(theta * rayN - uTimeS * uSpin * 2.4);\n rays = pow(clamp(rays, 0.0, 1.0), mix(2.2, 5.0, style)); // crisper on the cel end\n float rayMask = smoothstep(0.02, 0.16, rn) * (1.0 - smoothstep(0.30, 0.62, rn));\n vec3 rayCol = prideColor(theta / TAU + 0.5 + uSeed, style);\n col += rayCol * rays * rayMask * gain * 0.5;\n\n // ---- 2. RAINBOW SWOOSH: an expanding shockwave ring whose hue cycles with\n // angle. It bursts outward over life and widens as it goes \u2014 the \"swoosh\". ----\n float front = uSwoosh * (0.10 + 0.78 * uLife);\n float width = 0.035 + 0.16 * uLife;\n float dr = (rn - front) / max(width, 1e-3);\n float ring = exp(-dr * dr);\n vec3 ringCol = prideColor(theta / TAU + uSpin * uTimeS * 0.15 + uSeed, style);\n col += ringCol * ring * gain * 1.35;\n // a brighter leading lip on the ring's outer edge (the wet swoosh shine)\n col += vec3(1.0) * smoothstep(0.0, 1.0, ring) * smoothstep(0.0, -1.2, dr) * gain * 0.35;\n\n // ---- 3. THE QUEEN: filled with a vertical rainbow + a hot white edge. ----\n float R = minDim * uSizeFrac;\n float scale = mix(0.34, 1.0, clamp(uPop, 0.0, 1.4));\n vec2 q = rel / max(R, 1e-3) / max(scale, 1e-3);\n float dq = queenDist(q);\n float aa = 1.6 / max(R, 1e-3);\n float fill = smoothstep(aa, -aa, dq);\n float edge = smoothstep(aa * 2.5, 0.0, abs(dq)); // bright rim line\n float halo = exp(-max(dq, 0.0) / 0.06); // soft outer glow\n // vertical rainbow over the piece + a slow shimmer; whimsy \u2192 flag stripes.\n float qt = q.y * 0.42 + 0.5 + uSeed + uTimeS * 0.05;\n vec3 body = prideColor(qt, style);\n vec3 queenCol = body * fill * 1.45 // saturated rainbow fill\n + mix(body, vec3(1.0), 0.6) * edge * 0.8 // bright (tinted) edge\n + body * halo * 0.55; // coloured glow\n // a brief white core just after the bounce so she \"lands\" with a flash\n queenCol += vec3(1.0) * fill * (1.0 - smoothstep(0.0, 0.22, uLife)) * 0.35;\n col += queenCol * (uExposure * (0.35 + 0.65 * uAmp));\n\n // ---- 4. SPARKLE BLING: twinkling 4-point stars riding outward. ----\n float sparkleReach = (0.16 + 0.62 * uLife) * minDim;\n vec3 bling = vec3(0.0);\n for (int i = 0; i < MAX_SPARKLES; i++) {\n float fi = float(i);\n vec2 h = hash21(fi * 3.17 + uSeed * 31.0);\n float ang = h.x * TAU;\n float rad = (0.45 + 0.55 * h.y) * sparkleReach; // spread out as life grows\n vec2 pos = uOrigin + vec2(cos(ang), sin(ang)) * rad;\n // twinkle: each sparkle blinks on its own phase\n float ph = h.x * 17.0 + h.y * 9.0;\n float tw = pow(0.5 + 0.5 * sin(uTimeS * 7.0 + ph), mix(3.0, 7.0, style));\n float sz = minDim * (0.012 + 0.018 * h.y) * (0.8 + 0.5 * uBling);\n float g = starGlint(frag, pos, sz) * tw;\n // tint with the per-fire accent palette, biased bright (white-hot core).\n vec3 tint = mix(vec3(1.0), paletteMix(fract(fi * 0.137 + uSeed)), 0.55);\n bling += tint * g;\n }\n col += bling * uBling * gain * 0.9;\n\n // ---- 5. POP FLASH: a hot radial flash at the instant of the bounce. ----\n float flashT = 1.0 - smoothstep(0.0, 0.22, uLife);\n float flash = exp(-rn * rn * 26.0) * flashT;\n col += mix(vec3(1.0), prideColor(uTimeS * 0.3 + uSeed, style), 0.4) * flash * uExposure * 1.4;\n\n // ---- Filmic tonemap + finishing ----\n col = tonemapACES(col * 0.95);\n // Ordered dither (~1/255) to kill banding the screen blend reveals; faded out\n // toward the cel/pop-art end where hard flag bands are intended.\n col = ditherAdd(col, frag, uTimeS, 1.0 - style);\n\n fragColor = vec4(max(col, 0.0), 1.0);\n}";
32
+ //# sourceMappingURL=checkmate-shader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkmate-shader.d.ts","sourceRoot":"","sources":["../src/checkmate-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,oFAAoF;AACpF,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,eAAO,MAAM,oBAAoB,uPAK/B,CAAC;AAEH,eAAO,MAAM,sBAAsB,w1VAuNjC,CAAC"}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * GLSL ES 3.00 source for **Checkmate** — an unapologetically fabulous winning
3
+ * move. A chess QUEEN pops into place with an overshoot bounce and the whole
4
+ * frame ERUPTS in LGBTQ+ pride: an expanding rainbow swoosh-shockwave, a
5
+ * spinning pride sunburst, and a mob of twinkling 4-point sparkle bling.
6
+ *
7
+ * Authored ONCE here; the toolchain transpiles the MSL + Kotlin variants from
8
+ * this single GLSL (`x-build.shader`). The chess queen is drawn ANALYTICALLY
9
+ * from 2D SDF primitives (a flared trapezoid body + base, a collar bar and five
10
+ * crown balls on stems) so it is byte-identical on every backend — no baked SDF,
11
+ * no sampler, no per-platform fallback.
12
+ *
13
+ * Layers, summed as light (canvas is black, `mix-blend-mode: screen`, so black
14
+ * == no change, bright == cast light onto the page beneath):
15
+ * 1. SUNBURST — radial pride rays spinning behind the queen (uSpin/uRays).
16
+ * 2. SWOOSH — an expanding rainbow shockwave ring whose hue cycles with
17
+ * angle; it bursts outward over life (the "swoosh").
18
+ * 3. QUEEN — the chess piece, filled with a vertical rainbow gradient +
19
+ * a hot white edge, popped in by uPop (overshoot bounce).
20
+ * 4. BLING — a scatter of twinkling 4-point star glints riding outward
21
+ * with the swoosh (the sparkle "bling"), tinted by the palette.
22
+ * 5. FLASH — a hot radial flash at the instant of the pop.
23
+ *
24
+ * whimsy == uStyle: 0 = smooth photoreal spectral glow; 1 = cel POP-ART — the
25
+ * rainbow posterizes into the canonical 6-stripe pride flag and the bling reads
26
+ * as chunky comic stars. The pass runner already snaps the clock "on twos".
27
+ */
28
+ import { GLSL_CONSTANTS, GLSL_DITHER, GLSL_HASH, GLSL_PALETTE_MIX, GLSL_SD_SEG, GLSL_TONEMAP_ACES, } from "@dopaminefx/core";
29
+ /** Scatter count for the sparkle bling. Single source of truth for the loop cap. */
30
+ export const MAX_SPARKLES = 16;
31
+ export const CHECKMATE_VERTEX_SRC = /* glsl */ `#version 300 es
32
+ void main() {
33
+ // Single full-screen triangle from gl_VertexID — no vertex buffers needed.
34
+ vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
35
+ gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
36
+ }`;
37
+ export const CHECKMATE_FRAGMENT_SRC = /* glsl */ `#version 300 es
38
+ precision highp float;
39
+ out vec4 fragColor;
40
+
41
+ uniform vec2 uResolution; // device pixels
42
+ uniform vec2 uOrigin; // queen anchor, gl coords (y up)
43
+ uniform float uAmp; // held-breath envelope amplitude (brightness gate)
44
+ uniform float uPop; // easeOutBack pop scale (overshoot bounce -> 1)
45
+ uniform float uLife; // whole-effect progress 0..1
46
+ uniform float uTimeS; // elapsed seconds (snapped "on twos" by style)
47
+ uniform float uExposure; // overall light gain (intensity)
48
+ uniform float uBling; // sparkle density/brightness (intensity)
49
+ uniform float uSwoosh; // rainbow shockwave reach (intensity)
50
+ uniform float uRays; // pride sunburst ray count (integer)
51
+ uniform float uSpin; // sunburst/swoosh rotation speed
52
+ uniform float uSizeFrac; // queen box size as a fraction of min viewport dim
53
+ uniform float uSeed; // per-fire scatter/hue offset
54
+ uniform float uStyle; // 0..1 photoreal spectral -> cel pop-art pride flag
55
+ uniform float uShadow; // 0 = light pass (screen), 1 = shadow pass (multiply)
56
+ uniform vec2 uShadowOffset; // device-px offset of the cast silhouette
57
+ uniform float uShadowSoft; // penumbra softness in device px (blur tap radius)
58
+ uniform float uShadowStrength;// 0..1 max darkening of the multiply layer
59
+ uniform vec3 uC0; // accent palette (sparkle tint) — per fire
60
+ uniform vec3 uC1; // mid
61
+ uniform vec3 uC2; // outer accent
62
+
63
+ #define MAX_SPARKLES ${MAX_SPARKLES}
64
+ ${GLSL_CONSTANTS}
65
+ ${GLSL_HASH}
66
+ ${GLSL_PALETTE_MIX}
67
+ ${GLSL_SD_SEG}
68
+ ${GLSL_TONEMAP_ACES}
69
+ ${GLSL_DITHER}
70
+
71
+ // ---- The pride spectrum -----------------------------------------------------
72
+ // Smooth IQ-cosine rainbow: a continuous, saturated spectral sweep over [0,1).
73
+ vec3 prideSmooth(float t){
74
+ t = fract(t);
75
+ return 0.5 + 0.5 * cos(TAU * (t + vec3(0.0, 0.33, 0.67)));
76
+ }
77
+ // The canonical 6-stripe pride FLAG, posterized from t (red→violet).
78
+ vec3 prideFlag(float t){
79
+ t = fract(t);
80
+ if (t < 0.16667) return vec3(0.94, 0.10, 0.12); // red
81
+ if (t < 0.33333) return vec3(1.00, 0.55, 0.06); // orange
82
+ if (t < 0.50000) return vec3(1.00, 0.93, 0.10); // yellow
83
+ if (t < 0.66667) return vec3(0.18, 0.70, 0.22); // green
84
+ if (t < 0.83333) return vec3(0.10, 0.36, 0.90); // blue
85
+ return vec3(0.46, 0.12, 0.62); // violet
86
+ }
87
+ // Blend smooth↔flag by whimsy (style): cel end snaps to the 6 flag stripes.
88
+ vec3 prideColor(float t, float style){
89
+ return mix(prideSmooth(t), prideFlag(t), style);
90
+ }
91
+
92
+ // ---- 2D SDF primitives for the chess QUEEN silhouette -----------------------
93
+ float sdBox(vec2 p, vec2 b){
94
+ vec2 d = abs(p) - b;
95
+ return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
96
+ }
97
+ // Inigo Quilez trapezoid SDF: half-widths r1 (bottom) and r2 (top), half-height he.
98
+ float sdTrapezoid(vec2 p, float r1, float r2, float he){
99
+ vec2 k1 = vec2(r2, he);
100
+ vec2 k2 = vec2(r2 - r1, 2.0 * he);
101
+ p.x = abs(p.x);
102
+ vec2 ca = vec2(p.x - min(p.x, (p.y < 0.0) ? r1 : r2), abs(p.y) - he);
103
+ vec2 cb = p - k1 + k2 * clamp(dot(k1 - p, k2) / dot(k2, k2), 0.0, 1.0);
104
+ float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;
105
+ return s * sqrt(min(dot(ca, ca), dot(cb, cb)));
106
+ }
107
+ // Signed distance to the queen, in LOCAL units (q centered, y up, ~[-1,1]).
108
+ // Union (min) of: base foot, flared body, collar band, five crown balls + stems.
109
+ float queenDist(vec2 q){
110
+ float d = sdTrapezoid(q - vec2(0.0, -0.74), 0.60, 0.40, 0.12); // base foot
111
+ d = min(d, sdTrapezoid(q - vec2(0.0, -0.10), 0.46, 0.15, 0.50)); // flared body
112
+ d = min(d, sdBox(q - vec2(0.0, 0.40), vec2(0.32, 0.05)) - 0.02); // collar band
113
+ // crown balls (center tallest) + the stems joining them to the band
114
+ d = min(d, length(q - vec2(-0.46, 0.55)) - 0.115);
115
+ d = min(d, length(q - vec2(-0.23, 0.62)) - 0.125);
116
+ d = min(d, length(q - vec2( 0.00, 0.71)) - 0.145);
117
+ d = min(d, length(q - vec2( 0.23, 0.62)) - 0.125);
118
+ d = min(d, length(q - vec2( 0.46, 0.55)) - 0.115);
119
+ d = min(d, sdSeg(q, vec2(-0.46, 0.55), vec2(-0.28, 0.42)) - 0.045);
120
+ d = min(d, sdSeg(q, vec2(-0.23, 0.62), vec2(-0.14, 0.42)) - 0.045);
121
+ d = min(d, sdSeg(q, vec2( 0.00, 0.71), vec2( 0.00, 0.42)) - 0.055);
122
+ d = min(d, sdSeg(q, vec2( 0.23, 0.62), vec2( 0.14, 0.42)) - 0.045);
123
+ d = min(d, sdSeg(q, vec2( 0.46, 0.55), vec2( 0.28, 0.42)) - 0.045);
124
+ return d;
125
+ }
126
+
127
+ // A twinkling 4-point star glint: hot core + two soft anisotropic spikes.
128
+ float starGlint(vec2 p, vec2 c, float size){
129
+ vec2 d = (p - c) / max(size, 1e-3);
130
+ float r = length(d);
131
+ float core = exp(-r * r * 5.0);
132
+ float sx = exp(-abs(d.x) * 6.0) * exp(-abs(d.y) * 1.4);
133
+ float sy = exp(-abs(d.y) * 6.0) * exp(-abs(d.x) * 1.4);
134
+ return core + (sx + sy) * 0.7;
135
+ }
136
+
137
+ // ---- Queen coverage (0..1 fill) at a fragment, with the pop scale applied ----
138
+ float queenFill(vec2 frag){
139
+ float R = min(uResolution.x, uResolution.y) * uSizeFrac;
140
+ float scale = mix(0.34, 1.0, clamp(uPop, 0.0, 1.4)); // bounce-in scale
141
+ vec2 q = (frag - uOrigin) / max(R, 1e-3) / max(scale, 1e-3);
142
+ float d = queenDist(q);
143
+ float aa = 1.6 / max(R, 1e-3); // ~1.6 device px, local units
144
+ return smoothstep(aa, -aa, d);
145
+ }
146
+
147
+ // ---- SHADOW silhouette — the queen casts a soft offset occlusion. -----------
148
+ float occlusion(vec2 frag){
149
+ return clamp(queenFill(frag) * uAmp, 0.0, 1.0);
150
+ }
151
+ vec4 shadowColor(vec2 frag){
152
+ vec2 sp = frag - uShadowOffset;
153
+ float s = uShadowSoft;
154
+ float occ = occlusion(sp);
155
+ occ += occlusion(sp + vec2(s, 0.0));
156
+ occ += occlusion(sp + vec2(-s, 0.0));
157
+ occ += occlusion(sp + vec2(0.0, s));
158
+ occ += occlusion(sp + vec2(0.0, -s));
159
+ occ /= 5.0;
160
+ float dark = clamp(occ, 0.0, 1.0) * uShadowStrength;
161
+ // A faintly warm, regal shadow tint (not a flat grey).
162
+ vec3 tint = mix(vec3(1.0), vec3(0.74, 0.70, 0.78), 1.0);
163
+ vec3 mul = mix(vec3(1.0), tint, dark);
164
+ return vec4(mul, 1.0);
165
+ }
166
+
167
+ void main(){
168
+ vec2 frag = gl_FragCoord.xy;
169
+ float minDim = min(uResolution.x, uResolution.y);
170
+
171
+ if (uShadow > 0.5) { fragColor = shadowColor(frag); return; }
172
+
173
+ vec2 rel = frag - uOrigin;
174
+ float r = length(rel);
175
+ float rn = r / minDim; // normalized radius
176
+ float theta = atan(rel.y, rel.x); // -PI..PI
177
+ float gain = uAmp * uExposure;
178
+ float style = uStyle;
179
+
180
+ vec3 col = vec3(0.0);
181
+
182
+ // ---- 1. PRIDE SUNBURST: spinning radial rays behind the queen. ----
183
+ float rayN = max(uRays, 1.0);
184
+ float rays = 0.5 + 0.5 * cos(theta * rayN - uTimeS * uSpin * 2.4);
185
+ rays = pow(clamp(rays, 0.0, 1.0), mix(2.2, 5.0, style)); // crisper on the cel end
186
+ float rayMask = smoothstep(0.02, 0.16, rn) * (1.0 - smoothstep(0.30, 0.62, rn));
187
+ vec3 rayCol = prideColor(theta / TAU + 0.5 + uSeed, style);
188
+ col += rayCol * rays * rayMask * gain * 0.5;
189
+
190
+ // ---- 2. RAINBOW SWOOSH: an expanding shockwave ring whose hue cycles with
191
+ // angle. It bursts outward over life and widens as it goes — the "swoosh". ----
192
+ float front = uSwoosh * (0.10 + 0.78 * uLife);
193
+ float width = 0.035 + 0.16 * uLife;
194
+ float dr = (rn - front) / max(width, 1e-3);
195
+ float ring = exp(-dr * dr);
196
+ vec3 ringCol = prideColor(theta / TAU + uSpin * uTimeS * 0.15 + uSeed, style);
197
+ col += ringCol * ring * gain * 1.35;
198
+ // a brighter leading lip on the ring's outer edge (the wet swoosh shine)
199
+ col += vec3(1.0) * smoothstep(0.0, 1.0, ring) * smoothstep(0.0, -1.2, dr) * gain * 0.35;
200
+
201
+ // ---- 3. THE QUEEN: filled with a vertical rainbow + a hot white edge. ----
202
+ float R = minDim * uSizeFrac;
203
+ float scale = mix(0.34, 1.0, clamp(uPop, 0.0, 1.4));
204
+ vec2 q = rel / max(R, 1e-3) / max(scale, 1e-3);
205
+ float dq = queenDist(q);
206
+ float aa = 1.6 / max(R, 1e-3);
207
+ float fill = smoothstep(aa, -aa, dq);
208
+ float edge = smoothstep(aa * 2.5, 0.0, abs(dq)); // bright rim line
209
+ float halo = exp(-max(dq, 0.0) / 0.06); // soft outer glow
210
+ // vertical rainbow over the piece + a slow shimmer; whimsy → flag stripes.
211
+ float qt = q.y * 0.42 + 0.5 + uSeed + uTimeS * 0.05;
212
+ vec3 body = prideColor(qt, style);
213
+ vec3 queenCol = body * fill * 1.45 // saturated rainbow fill
214
+ + mix(body, vec3(1.0), 0.6) * edge * 0.8 // bright (tinted) edge
215
+ + body * halo * 0.55; // coloured glow
216
+ // a brief white core just after the bounce so she "lands" with a flash
217
+ queenCol += vec3(1.0) * fill * (1.0 - smoothstep(0.0, 0.22, uLife)) * 0.35;
218
+ col += queenCol * (uExposure * (0.35 + 0.65 * uAmp));
219
+
220
+ // ---- 4. SPARKLE BLING: twinkling 4-point stars riding outward. ----
221
+ float sparkleReach = (0.16 + 0.62 * uLife) * minDim;
222
+ vec3 bling = vec3(0.0);
223
+ for (int i = 0; i < MAX_SPARKLES; i++) {
224
+ float fi = float(i);
225
+ vec2 h = hash21(fi * 3.17 + uSeed * 31.0);
226
+ float ang = h.x * TAU;
227
+ float rad = (0.45 + 0.55 * h.y) * sparkleReach; // spread out as life grows
228
+ vec2 pos = uOrigin + vec2(cos(ang), sin(ang)) * rad;
229
+ // twinkle: each sparkle blinks on its own phase
230
+ float ph = h.x * 17.0 + h.y * 9.0;
231
+ float tw = pow(0.5 + 0.5 * sin(uTimeS * 7.0 + ph), mix(3.0, 7.0, style));
232
+ float sz = minDim * (0.012 + 0.018 * h.y) * (0.8 + 0.5 * uBling);
233
+ float g = starGlint(frag, pos, sz) * tw;
234
+ // tint with the per-fire accent palette, biased bright (white-hot core).
235
+ vec3 tint = mix(vec3(1.0), paletteMix(fract(fi * 0.137 + uSeed)), 0.55);
236
+ bling += tint * g;
237
+ }
238
+ col += bling * uBling * gain * 0.9;
239
+
240
+ // ---- 5. POP FLASH: a hot radial flash at the instant of the bounce. ----
241
+ float flashT = 1.0 - smoothstep(0.0, 0.22, uLife);
242
+ float flash = exp(-rn * rn * 26.0) * flashT;
243
+ col += mix(vec3(1.0), prideColor(uTimeS * 0.3 + uSeed, style), 0.4) * flash * uExposure * 1.4;
244
+
245
+ // ---- Filmic tonemap + finishing ----
246
+ col = tonemapACES(col * 0.95);
247
+ // Ordered dither (~1/255) to kill banding the screen blend reveals; faded out
248
+ // toward the cel/pop-art end where hard flag bands are intended.
249
+ col = ditherAdd(col, frag, uTimeS, 1.0 - style);
250
+
251
+ fragColor = vec4(max(col, 0.0), 1.0);
252
+ }`;
253
+ //# sourceMappingURL=checkmate-shader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkmate-shader.js","sourceRoot":"","sources":["../src/checkmate-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,cAAc,EACd,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,oFAAoF;AACpF,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAE/B,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;;;;;EAK7C,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;uBA0B1B,YAAY;EACjC,cAAc;EACd,SAAS;EACT,gBAAgB;EAChB,WAAW;EACX,iBAAiB;EACjB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuLX,CAAC"}
@@ -0,0 +1,354 @@
1
+ {
2
+ "fmt": "dopamine-effect",
3
+ "v": "1.0.0",
4
+ "id": "dopamine.success.checkmate",
5
+ "meta": {
6
+ "name": "Checkmate",
7
+ "description": "An unapologetically fabulous winning move: a chess QUEEN pops into place with an overshoot bounce, hurls an expanding RAINBOW swoosh-shockwave + a spinning pride sunburst, and is mobbed by twinkling 4-point sparkle bling. LGBTQ+ pride inspired (yas, queen). Heavy on whimsy + bling; high whimsy posterizes the rainbow into the canonical 6-stripe pride flag.",
8
+ "tags": [
9
+ "success",
10
+ "celebration",
11
+ "pride",
12
+ "chess",
13
+ "bling"
14
+ ]
15
+ },
16
+ "controls": {
17
+ "mood": {
18
+ "type": "enum",
19
+ "label": "Mood",
20
+ "default": "celebratory",
21
+ "options": [
22
+ "serene",
23
+ "celebratory",
24
+ "electric"
25
+ ],
26
+ "ui": "segmented"
27
+ },
28
+ "intensity": {
29
+ "type": "scalar",
30
+ "label": "Intensity",
31
+ "default": 0.75,
32
+ "min": 0,
33
+ "max": 1,
34
+ "step": 0.01,
35
+ "ui": "slider",
36
+ "help": "Reward strength: pop overshoot, swoosh reach, sunburst energy, sparkle density + brightness."
37
+ },
38
+ "whimsy": {
39
+ "type": "scalar",
40
+ "label": "Whimsy",
41
+ "default": 0.6,
42
+ "min": 0,
43
+ "max": 1,
44
+ "step": 0.01,
45
+ "ui": "slider",
46
+ "help": "Photoreal smooth spectral glow (0) to a cel POP-ART pride flag (1): the rainbow posterizes into the canonical 6 stripes and the motion snaps on twos."
47
+ },
48
+ "seed": {
49
+ "type": "int",
50
+ "label": "Seed",
51
+ "default": null,
52
+ "nullable": true,
53
+ "help": "Null = unique sparkle scatter + accent palette per fire; pin to reproduce."
54
+ },
55
+ "origin": {
56
+ "type": "point",
57
+ "label": "Origin",
58
+ "default": "center"
59
+ },
60
+ "target": {
61
+ "type": "selector",
62
+ "label": "Target",
63
+ "default": "document.body"
64
+ }
65
+ },
66
+ "baselines": {
67
+ "serene": {
68
+ "durationMs": 2200,
69
+ "lightness": 0.88,
70
+ "chroma": 0.1,
71
+ "hueCenter": 300,
72
+ "hueRange": 320,
73
+ "bling": 0.7,
74
+ "swoosh": 0.82,
75
+ "rays": 8,
76
+ "spin": 0.4,
77
+ "sizeFrac": 0.26
78
+ },
79
+ "celebratory": {
80
+ "durationMs": 1700,
81
+ "lightness": 0.85,
82
+ "chroma": 0.18,
83
+ "hueCenter": 320,
84
+ "hueRange": 360,
85
+ "bling": 1,
86
+ "swoosh": 1,
87
+ "rays": 10,
88
+ "spin": 0.7,
89
+ "sizeFrac": 0.3
90
+ },
91
+ "electric": {
92
+ "durationMs": 1300,
93
+ "lightness": 0.82,
94
+ "chroma": 0.26,
95
+ "hueCenter": 330,
96
+ "hueRange": 300,
97
+ "bling": 1.35,
98
+ "swoosh": 1.2,
99
+ "rays": 12,
100
+ "spin": 1.05,
101
+ "sizeFrac": 0.34
102
+ }
103
+ },
104
+ "palette": {
105
+ "model": "oklch",
106
+ "space": "linear-srgb",
107
+ "generator": "golden-angle",
108
+ "goldenAngleDeg": 137.50776405003785,
109
+ "stops": 3,
110
+ "hueSpread": 0.7,
111
+ "lightness": {
112
+ "baseline": "lightness",
113
+ "perStop": [
114
+ 0,
115
+ 0.06,
116
+ -0.05
117
+ ]
118
+ },
119
+ "chroma": {
120
+ "from": {
121
+ "mul": [
122
+ {
123
+ "baseline": "chroma"
124
+ },
125
+ {
126
+ "lerp": [
127
+ "intensity",
128
+ 0.7,
129
+ 1.5
130
+ ]
131
+ }
132
+ ]
133
+ },
134
+ "perStop": [
135
+ 0,
136
+ 0.02,
137
+ -0.01
138
+ ]
139
+ },
140
+ "seed": {
141
+ "deterministic": true,
142
+ "source": "controls.seed",
143
+ "prng": "mulberry32",
144
+ "note": "Per-fire accent palette (used to TINT the sparkle bling) drawn from a wide hueRange so the glints sparkle in shifting colours. The hero rainbow is the procedural pride spectrum in the shader (independent of this register); this keeps the seed/parity machinery + per-fire novelty honest."
145
+ },
146
+ "perMood": {
147
+ "serene": {
148
+ "hueCenter": 300,
149
+ "hueRange": 320,
150
+ "lightness": 0.88,
151
+ "chroma": 0.1
152
+ },
153
+ "celebratory": {
154
+ "hueCenter": 320,
155
+ "hueRange": 360,
156
+ "lightness": 0.85,
157
+ "chroma": 0.18
158
+ },
159
+ "electric": {
160
+ "hueCenter": 330,
161
+ "hueRange": 300,
162
+ "lightness": 0.82,
163
+ "chroma": 0.26
164
+ }
165
+ }
166
+ },
167
+ "tempo": {
168
+ "durationMs": {
169
+ "from": {
170
+ "round": {
171
+ "mul": [
172
+ {
173
+ "baseline": "durationMs"
174
+ },
175
+ {
176
+ "lerp": [
177
+ "intensity",
178
+ 1.1,
179
+ 0.9
180
+ ]
181
+ }
182
+ ]
183
+ }
184
+ }
185
+ },
186
+ "frame": {
187
+ "amp": {
188
+ "envelope": [
189
+ {
190
+ "input": "life"
191
+ },
192
+ {
193
+ "param": "overshoot"
194
+ }
195
+ ]
196
+ },
197
+ "extras": {
198
+ "pop": {
199
+ "easeOutBack": [
200
+ {
201
+ "clamp01": {
202
+ "div": [
203
+ {
204
+ "input": "life"
205
+ },
206
+ 0.28
207
+ ]
208
+ }
209
+ },
210
+ {
211
+ "param": "overshoot"
212
+ }
213
+ ]
214
+ }
215
+ }
216
+ },
217
+ "reducedMotion": {
218
+ "peakMs": 320,
219
+ "holdMs": 420
220
+ },
221
+ "note": "amp is the held-breath success envelope (brightness gate, decays to 0 — a transient reward). pop is a separate easeOutBack scale over the first 28% of life that OVERSHOOTS then settles to exactly 1 and HOLDS, so the queen bounces into place and stays full-size while the light fades around her."
222
+ },
223
+ "render": {
224
+ "params": {
225
+ "exposure": {
226
+ "type": "float",
227
+ "from": {
228
+ "lerp": [
229
+ "intensity",
230
+ 0.9,
231
+ 1.6
232
+ ]
233
+ }
234
+ },
235
+ "bling": {
236
+ "type": "float",
237
+ "from": {
238
+ "mul": [
239
+ {
240
+ "baseline": "bling"
241
+ },
242
+ {
243
+ "lerp": [
244
+ "intensity",
245
+ 0.7,
246
+ 1.4
247
+ ]
248
+ }
249
+ ]
250
+ }
251
+ },
252
+ "swoosh": {
253
+ "type": "float",
254
+ "from": {
255
+ "mul": [
256
+ {
257
+ "baseline": "swoosh"
258
+ },
259
+ {
260
+ "lerp": [
261
+ "intensity",
262
+ 0.8,
263
+ 1.3
264
+ ]
265
+ }
266
+ ]
267
+ }
268
+ },
269
+ "rays": {
270
+ "type": "int",
271
+ "from": {
272
+ "round": {
273
+ "baseline": "rays"
274
+ }
275
+ }
276
+ },
277
+ "spin": {
278
+ "type": "float",
279
+ "from": {
280
+ "mul": [
281
+ {
282
+ "baseline": "spin"
283
+ },
284
+ {
285
+ "lerp": [
286
+ "intensity",
287
+ 0.85,
288
+ 1.25
289
+ ]
290
+ }
291
+ ]
292
+ }
293
+ },
294
+ "sizeFrac": {
295
+ "type": "float",
296
+ "from": {
297
+ "baseline": "sizeFrac"
298
+ }
299
+ },
300
+ "overshoot": {
301
+ "type": "float",
302
+ "from": {
303
+ "lerp": [
304
+ "intensity",
305
+ 0.8,
306
+ 1.6
307
+ ]
308
+ }
309
+ },
310
+ "style": {
311
+ "type": "float",
312
+ "from": {
313
+ "control": "whimsy"
314
+ }
315
+ }
316
+ },
317
+ "shadowHeightFrac": 0.5,
318
+ "consts": {},
319
+ "config": {
320
+ "usesOrigin": true
321
+ },
322
+ "backends": {
323
+ "webgl2": {
324
+ "stage": "fullscreen-triangle",
325
+ "blend": "screen",
326
+ "shader": {
327
+ "program": "checkmate"
328
+ }
329
+ }
330
+ },
331
+ "fallbackOrder": [
332
+ "webgl2"
333
+ ]
334
+ },
335
+ "binding": {
336
+ "note": "CROSS-PLATFORM uniform-binding contract. Which render.params are NOT shader uniforms, the seed-keyed scatter field (uSeed seeds the sparkle scatter), and the per-frame extras (the pop scale). No samplers — the chess queen is drawn analytically from 2D SDF primitives so it is byte-identical on web/Metal/GL with no baked-SDF aux texture.",
337
+ "excludeParams": [
338
+ "style",
339
+ "overshoot",
340
+ "durationMs"
341
+ ],
342
+ "scatterKey": "checkmateSeed",
343
+ "scatterWeb": "uSeed",
344
+ "extras": [
345
+ {
346
+ "name": "pop",
347
+ "type": "float",
348
+ "web": "uPop",
349
+ "note": "easeOutBack pop scale 0→overshoot→1 (the queen's bounce-in, then held)"
350
+ }
351
+ ],
352
+ "samplers": []
353
+ }
354
+ }