jaml-ui 0.22.5 → 0.24.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.
@@ -1,85 +1,85 @@
1
- export const SEARCH_WORKER_CODE = `
2
- let MotelyWasm = null;
3
- let MotelyWasmEvents = null;
4
- let activeSearch = null;
5
-
6
- self.addEventListener('message', async function(e) {
7
- const msg = e.data;
8
-
9
- if (msg.type === 'init') {
10
- try {
11
- const mod = await import(msg.url);
12
- await mod.default.boot();
13
- const motely = mod.Motely;
14
- MotelyWasm = motely.MotelyWasm;
15
- MotelyWasmEvents = motely.MotelyWasmEvents;
16
- self.postMessage({ type: 'ready' });
17
- } catch (err) {
18
- self.postMessage({ type: 'error', message: String(err) });
19
- }
20
- return;
21
- }
22
-
23
- if (msg.type === 'start') {
24
- if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
25
- const validation = MotelyWasm.validateJaml(msg.jaml);
26
- if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }
27
-
28
- function cleanup() {
29
- MotelyWasmEvents.notifyResult = () => {};
30
- MotelyWasmEvents.notifyProgress = () => {};
31
- MotelyWasmEvents.notifyComplete = () => {};
32
- activeSearch = null;
33
- }
34
-
35
- MotelyWasmEvents.notifyResult = function(seed, score, tallyColumns) {
36
- self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });
37
- };
38
- MotelyWasmEvents.notifyProgress = function(searched, matching) {
39
- self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
40
- };
41
- MotelyWasmEvents.notifyComplete = function(status, searched, matched) {
42
- cleanup();
43
- self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });
44
- };
45
-
46
- try {
47
- const mode = msg.mode || 'random';
48
- if (mode === 'random') {
49
- activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
50
- } else if (mode === 'aesthetic') {
51
- activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
52
- } else if (mode === 'seedList') {
53
- activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
54
- } else if (mode === 'keyword') {
55
- activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');
56
- } else if (mode === 'sequential') {
57
- activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));
58
- } else {
59
- self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });
60
- cleanup();
61
- return;
62
- }
63
- } catch (err) {
64
- cleanup();
65
- self.postMessage({ type: 'error', message: String(err) });
66
- }
67
- return;
68
- }
69
-
70
- if (msg.type === 'stop') {
71
- if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
72
- self.postMessage({ type: 'cancelled' });
73
- }
74
-
75
- if (msg.type === 'get_tally_labels') {
76
- if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
77
- try {
78
- const labels = MotelyWasm.getTallyLabels(msg.jaml);
79
- self.postMessage({ type: 'tally_labels', labels: Array.from(labels) });
80
- } catch (err) {
81
- self.postMessage({ type: 'error', message: String(err) });
82
- }
83
- }
84
- });
1
+ export const SEARCH_WORKER_CODE = `
2
+ let MotelyWasm = null;
3
+ let MotelyWasmEvents = null;
4
+ let activeSearch = null;
5
+
6
+ self.addEventListener('message', async function(e) {
7
+ const msg = e.data;
8
+
9
+ if (msg.type === 'init') {
10
+ try {
11
+ const mod = await import(msg.url);
12
+ await mod.default.boot();
13
+ const motely = mod.Motely;
14
+ MotelyWasm = motely.MotelyWasm;
15
+ MotelyWasmEvents = motely.MotelyWasmEvents;
16
+ self.postMessage({ type: 'ready' });
17
+ } catch (err) {
18
+ self.postMessage({ type: 'error', message: String(err) });
19
+ }
20
+ return;
21
+ }
22
+
23
+ if (msg.type === 'start') {
24
+ if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
25
+ const validation = MotelyWasm.validateJaml(msg.jaml);
26
+ if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }
27
+
28
+ function cleanup() {
29
+ MotelyWasmEvents.notifyResult = () => {};
30
+ MotelyWasmEvents.notifyProgress = () => {};
31
+ MotelyWasmEvents.notifyComplete = () => {};
32
+ activeSearch = null;
33
+ }
34
+
35
+ MotelyWasmEvents.notifyResult = function(seed, score, tallyColumns) {
36
+ self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });
37
+ };
38
+ MotelyWasmEvents.notifyProgress = function(searched, matching) {
39
+ self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
40
+ };
41
+ MotelyWasmEvents.notifyComplete = function(status, searched, matched) {
42
+ cleanup();
43
+ self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });
44
+ };
45
+
46
+ try {
47
+ const mode = msg.mode || 'random';
48
+ if (mode === 'random') {
49
+ activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
50
+ } else if (mode === 'aesthetic') {
51
+ activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
52
+ } else if (mode === 'seedList') {
53
+ activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
54
+ } else if (mode === 'keyword') {
55
+ activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');
56
+ } else if (mode === 'sequential') {
57
+ activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));
58
+ } else {
59
+ self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });
60
+ cleanup();
61
+ return;
62
+ }
63
+ } catch (err) {
64
+ cleanup();
65
+ self.postMessage({ type: 'error', message: String(err) });
66
+ }
67
+ return;
68
+ }
69
+
70
+ if (msg.type === 'stop') {
71
+ if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
72
+ self.postMessage({ type: 'cancelled' });
73
+ }
74
+
75
+ if (msg.type === 'get_tally_labels') {
76
+ if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
77
+ try {
78
+ const labels = MotelyWasm.getTallyLabels(msg.jaml);
79
+ self.postMessage({ type: 'tally_labels', labels: Array.from(labels) });
80
+ } catch (err) {
81
+ self.postMessage({ type: 'error', message: String(err) });
82
+ }
83
+ }
84
+ });
85
85
  `;
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
  import { useState, useCallback, useRef, useEffect } from "react";
3
- import { SEARCH_WORKER_CODE } from "./searchWorkerCode.js";
4
3
  const INITIAL_STATE = {
5
4
  results: [],
6
5
  totalSearched: 0n,
@@ -10,6 +9,92 @@ const INITIAL_STATE = {
10
9
  seedsPerSecond: 0,
11
10
  tallyLabels: [],
12
11
  };
12
+ const SEARCH_WORKER_CODE = `
13
+ let MotelyWasm = null;
14
+ let MotelyWasmEvents = null;
15
+ let activeSearch = null;
16
+
17
+ self.addEventListener('message', async function(e) {
18
+ const msg = e.data;
19
+
20
+ if (msg.type === 'init') {
21
+ try {
22
+ const mod = await import(msg.url);
23
+ await mod.default.boot();
24
+ const motely = mod.Motely;
25
+ MotelyWasm = motely.MotelyWasm;
26
+ MotelyWasmEvents = motely.MotelyWasmEvents;
27
+ self.postMessage({ type: 'ready' });
28
+ } catch (err) {
29
+ self.postMessage({ type: 'error', message: String(err) });
30
+ }
31
+ return;
32
+ }
33
+
34
+ if (msg.type === 'start') {
35
+ if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
36
+ const validation = MotelyWasm.validateJaml(msg.jaml);
37
+ if (validation !== 'valid') { self.postMessage({ type: 'error', message: validation }); return; }
38
+
39
+ function cleanup() {
40
+ MotelyWasmEvents.notifyResult = () => {};
41
+ MotelyWasmEvents.notifyProgress = () => {};
42
+ MotelyWasmEvents.notifyComplete = () => {};
43
+ activeSearch = null;
44
+ }
45
+
46
+ MotelyWasmEvents.notifyResult = function(seed, score, tallyColumns) {
47
+ self.postMessage({ type: 'result', seed, score, tallyColumns: Array.from(tallyColumns) });
48
+ };
49
+ MotelyWasmEvents.notifyProgress = function(searched, matching) {
50
+ self.postMessage({ type: 'progress', searched: searched.toString(), matching: matching.toString() });
51
+ };
52
+ MotelyWasmEvents.notifyComplete = function(status, searched, matched) {
53
+ cleanup();
54
+ self.postMessage({ type: 'complete', status, searched: searched.toString(), matched: matched.toString() });
55
+ };
56
+
57
+ try {
58
+ const mode = msg.mode || 'random';
59
+
60
+ if (mode === 'random') {
61
+ activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
62
+ } else if (mode === 'aesthetic') {
63
+ activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
64
+ } else if (mode === 'seedList') {
65
+ activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
66
+ } else if (mode === 'keyword') {
67
+ activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || '');
68
+ } else if (mode === 'sequential') {
69
+ activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch), BigInt(msg.endBatch));
70
+ } else {
71
+ self.postMessage({ type: 'error', message: 'Unknown search mode: ' + mode });
72
+ cleanup();
73
+ return;
74
+ }
75
+ } catch (err) {
76
+ cleanup();
77
+ self.postMessage({ type: 'error', message: String(err) });
78
+ }
79
+ return;
80
+ }
81
+
82
+ if (msg.type === 'stop') {
83
+ if (activeSearch) { activeSearch.cancel(); activeSearch = null; }
84
+ self.postMessage({ type: 'cancelled' });
85
+ }
86
+
87
+ if (msg.type === 'get_tally_labels') {
88
+ if (!MotelyWasm) { self.postMessage({ type: 'error', message: 'Not initialized' }); return; }
89
+ try {
90
+ const labels = MotelyWasm.getTallyLabels(msg.jaml);
91
+ self.postMessage({ type: 'tally_labels', labels: Array.from(labels) });
92
+ } catch (err) {
93
+ self.postMessage({ type: 'error', message: String(err) });
94
+ }
95
+ }
96
+ });
97
+ `;
13
98
  function createWorker() {
14
99
  const blob = new Blob([SEARCH_WORKER_CODE], { type: "application/javascript" });
15
100
  return new Worker(URL.createObjectURL(blob), { type: "module" });
package/dist/index.d.ts CHANGED
@@ -22,7 +22,6 @@ export { extractVisualJamlItems, type JamlPreviewGroups, type JamlPreviewItem, t
22
22
  export { useMotelyStream, type StreamItem, type StreamState } from "./hooks/useShopStream.js";
23
23
  export { useSearch, type SearchResult, type SearchStatus, type UseSearchState, } from "./hooks/useSearch.js";
24
24
  export { useAnalyzer, type AnalyzerStatus, type AnalyzerLive, type MotelyJsRunState, } from "./hooks/useAnalyzer.js";
25
- export { SEARCH_WORKER_CODE } from "./hooks/searchWorkerCode.js";
26
25
  export { setMotelyEnums as setMotelyDisplayEnums } from "./motelyDisplay.js";
27
26
  export { setMotelyEnums as setMotelyDecoderEnums } from "./decode/motelyItemDecoder.js";
28
27
  export { JamlAestheticSelector, type JamlAestheticSelectorProps, type JamlAestheticOption, } from "./components/JamlAestheticSelector.js";
package/dist/index.js CHANGED
@@ -23,7 +23,6 @@ export { extractVisualJamlItems, } from "./utils/jamlMapPreview.js";
23
23
  export { useMotelyStream } from "./hooks/useShopStream.js";
24
24
  export { useSearch, } from "./hooks/useSearch.js";
25
25
  export { useAnalyzer, } from "./hooks/useAnalyzer.js";
26
- export { SEARCH_WORKER_CODE } from "./hooks/searchWorkerCode.js";
27
26
  // Setter pattern for motely-wasm runtime enums. Consumers boot motely-wasm
28
27
  // and call setMotelyEnums(Motely) once after boot so display/decoder helpers
29
28
  // can resolve enum keys without statically importing motely-wasm.
package/dist/ui/footer.js CHANGED
@@ -11,10 +11,10 @@ const SUITS = [
11
11
  * All styling via jimbo.css `.j-footer` classes — zero inline styles.
12
12
  */
13
13
  export function JimboBalatroFooter({ hidden = false, className = '' }) {
14
- return (_jsxs("div", { className: `j-footer ${hidden ? 'j-footer--hidden' : ''} ${className}`, children: [_jsx("div", { className: "j-footer__bar", children: _jsxs("p", { className: "j-footer__text", children: [_jsxs("span", { children: ["Not affiliated with LocalThunk or PlayStack \u2022", ' '] }), _jsx("a", { href: "https://store.steampowered.com/app/2379780/Balatro/", target: "_blank", rel: "noopener noreferrer", className: "j-footer__link", children: "BUY BALATRO" }), _jsxs("span", { children: [' ', "\u2022 Created with", ' '] }), _jsxs("span", { className: "j-footer__suits", children: [_jsx("span", { className: "j-footer__suit-stage", children: SUITS.map(({ char, kf }) => (_jsx("span", { className: "j-footer__suit-char", style: { animationName: kf }, children: char }, char))) }), ' ', "for the Balatro community"] })] }) }), _jsx("style", { children: `
15
- @keyframes jaml-heart { 0%{opacity:0;transform:scale(1)} 1%{opacity:1;transform:scale(1.45)} 3.5%{opacity:1;transform:scale(1)} 61.5%{opacity:1;transform:scale(1)} 62%{opacity:0} 100%{opacity:0} }
16
- @keyframes jaml-spade { 0%,61.5%{opacity:0} 62%{opacity:1;transform:scale(1.45)} 64.5%{opacity:1;transform:scale(1)} 71.5%{opacity:1} 72%{opacity:0} 100%{opacity:0} }
17
- @keyframes jaml-diamond { 0%,71.5%{opacity:0} 72%{opacity:1;transform:scale(1.45)} 74.5%{opacity:1;transform:scale(1)} 81.5%{opacity:1} 82%{opacity:0} 100%{opacity:0} }
18
- @keyframes jaml-club { 0%,81.5%{opacity:0} 82%{opacity:1;transform:scale(1.45)} 84.5%{opacity:1;transform:scale(1)} 95%{opacity:1} 96%{opacity:0} 100%{opacity:0} }
14
+ return (_jsxs("div", { className: `j-footer ${hidden ? 'j-footer--hidden' : ''} ${className}`, children: [_jsx("div", { className: "j-footer__bar", children: _jsxs("p", { className: "j-footer__text", children: [_jsxs("span", { children: ["Not affiliated with LocalThunk or PlayStack \u2022", ' '] }), _jsx("a", { href: "https://store.steampowered.com/app/2379780/Balatro/", target: "_blank", rel: "noopener noreferrer", className: "j-footer__link", children: "BUY BALATRO" }), _jsxs("span", { children: [' ', "\u2022 Created with", ' '] }), _jsxs("span", { className: "j-footer__suits", children: [_jsx("span", { className: "j-footer__suit-stage", children: SUITS.map(({ char, kf }) => (_jsx("span", { className: "j-footer__suit-char", style: { animationName: kf }, children: char }, char))) }), ' ', "for the Balatro community"] })] }) }), _jsx("style", { children: `
15
+ @keyframes jaml-heart { 0%{opacity:0;transform:scale(1)} 1%{opacity:1;transform:scale(1.45)} 3.5%{opacity:1;transform:scale(1)} 61.5%{opacity:1;transform:scale(1)} 62%{opacity:0} 100%{opacity:0} }
16
+ @keyframes jaml-spade { 0%,61.5%{opacity:0} 62%{opacity:1;transform:scale(1.45)} 64.5%{opacity:1;transform:scale(1)} 71.5%{opacity:1} 72%{opacity:0} 100%{opacity:0} }
17
+ @keyframes jaml-diamond { 0%,71.5%{opacity:0} 72%{opacity:1;transform:scale(1.45)} 74.5%{opacity:1;transform:scale(1)} 81.5%{opacity:1} 82%{opacity:0} 100%{opacity:0} }
18
+ @keyframes jaml-club { 0%,81.5%{opacity:0} 82%{opacity:1;transform:scale(1.45)} 84.5%{opacity:1;transform:scale(1)} 95%{opacity:1} 96%{opacity:0} 100%{opacity:0} }
19
19
  ` })] }));
20
20
  }
package/dist/ui/hooks.js CHANGED
@@ -93,62 +93,62 @@ export function useBalatroBackground() {
93
93
  const gl = canvas.getContext('webgl');
94
94
  if (!gl)
95
95
  return;
96
- const vsSource = `
97
- attribute vec2 position;
98
- void main() {
99
- gl_Position = vec4(position, 0.0, 1.0);
100
- }
96
+ const vsSource = `
97
+ attribute vec2 position;
98
+ void main() {
99
+ gl_Position = vec4(position, 0.0, 1.0);
100
+ }
101
101
  `;
102
- const fsSource = `
103
- precision mediump float;
104
-
105
- uniform float u_time;
106
- uniform vec2 u_resolution;
107
-
108
- const float SPIN_ROTATION = -2.0;
109
- const float SPIN_SPEED = 4.5;
110
- const vec4 COLOUR_1 = vec4(1.0, 0.2, 0.2, 1.0);
111
- const vec4 COLOUR_2 = vec4(0.0, 0.5, 1.0, 1.0);
112
- const vec4 COLOUR_3 = vec4(0.05, 0.08, 0.1, 1.0);
113
- const float CONTRAST = 4.5;
114
- const float LIGTHING = 0.5;
115
- const float SPIN_AMOUNT = 0.35;
116
- const float PIXEL_FILTER = 1024.0;
117
- const float PI = 3.14159265359;
118
-
119
- void main() {
120
- vec2 screenSize = u_resolution;
121
- float pixel_size = length(screenSize.xy) / PIXEL_FILTER;
122
- vec2 uv = (floor(gl_FragCoord.xy*(1.0/pixel_size))*pixel_size - 0.5*screenSize.xy)/length(screenSize.xy);
123
- float uv_len = length(uv);
124
-
125
- float speed = (SPIN_ROTATION * 0.2) + 302.2;
126
- float new_pixel_angle = atan(uv.y, uv.x) + speed - 20.0*(1.0*SPIN_AMOUNT*uv_len + (1.0 - 1.0*SPIN_AMOUNT));
127
-
128
- vec2 mid = (screenSize.xy/length(screenSize.xy))/2.0;
129
- uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
130
-
131
- uv *= 30.0;
132
- speed = u_time * SPIN_SPEED;
133
- vec2 uv2 = vec2(uv.x, uv.y);
134
-
135
- for(int i=0; i < 5; i++) {
136
- uv2 += sin(max(uv.x, uv.y)) + uv;
137
- uv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121), sin(uv2.x - 0.113*speed));
138
- uv -= 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y);
139
- }
140
-
141
- float contrast_mod = (0.25*CONTRAST + 0.5*SPIN_AMOUNT + 1.2);
142
- float paint_res = min(2.0, max(0.0, length(uv)*(0.035)*contrast_mod));
143
- float c1p = max(0.0, 1.0 - contrast_mod*abs(1.0 - paint_res));
144
- float c2p = max(0.0, 1.0 - contrast_mod*abs(paint_res));
145
- float c3p = 1.0 - min(1.0, c1p + c2p);
146
- float light = (LIGTHING - 0.2)*max(c1p*5.0 - 4.0, 0.0) + LIGTHING*max(c2p*5.0 - 4.0, 0.0);
147
-
148
- vec4 finalCol = (0.3/CONTRAST)*COLOUR_1 + (1.0 - 0.3/CONTRAST)*(COLOUR_1*c1p + COLOUR_2*c2p + vec4(c3p*COLOUR_3.rgb, c3p*COLOUR_1.a)) + light;
149
-
150
- gl_FragColor = finalCol;
151
- }
102
+ const fsSource = `
103
+ precision mediump float;
104
+
105
+ uniform float u_time;
106
+ uniform vec2 u_resolution;
107
+
108
+ const float SPIN_ROTATION = -2.0;
109
+ const float SPIN_SPEED = 4.5;
110
+ const vec4 COLOUR_1 = vec4(1.0, 0.2, 0.2, 1.0);
111
+ const vec4 COLOUR_2 = vec4(0.0, 0.5, 1.0, 1.0);
112
+ const vec4 COLOUR_3 = vec4(0.05, 0.08, 0.1, 1.0);
113
+ const float CONTRAST = 4.5;
114
+ const float LIGTHING = 0.5;
115
+ const float SPIN_AMOUNT = 0.35;
116
+ const float PIXEL_FILTER = 1024.0;
117
+ const float PI = 3.14159265359;
118
+
119
+ void main() {
120
+ vec2 screenSize = u_resolution;
121
+ float pixel_size = length(screenSize.xy) / PIXEL_FILTER;
122
+ vec2 uv = (floor(gl_FragCoord.xy*(1.0/pixel_size))*pixel_size - 0.5*screenSize.xy)/length(screenSize.xy);
123
+ float uv_len = length(uv);
124
+
125
+ float speed = (SPIN_ROTATION * 0.2) + 302.2;
126
+ float new_pixel_angle = atan(uv.y, uv.x) + speed - 20.0*(1.0*SPIN_AMOUNT*uv_len + (1.0 - 1.0*SPIN_AMOUNT));
127
+
128
+ vec2 mid = (screenSize.xy/length(screenSize.xy))/2.0;
129
+ uv = (vec2((uv_len * cos(new_pixel_angle) + mid.x), (uv_len * sin(new_pixel_angle) + mid.y)) - mid);
130
+
131
+ uv *= 30.0;
132
+ speed = u_time * SPIN_SPEED;
133
+ vec2 uv2 = vec2(uv.x, uv.y);
134
+
135
+ for(int i=0; i < 5; i++) {
136
+ uv2 += sin(max(uv.x, uv.y)) + uv;
137
+ uv += 0.5*vec2(cos(5.1123314 + 0.353*uv2.y + speed*0.131121), sin(uv2.x - 0.113*speed));
138
+ uv -= 1.0*cos(uv.x + uv.y) - 1.0*sin(uv.x*0.711 - uv.y);
139
+ }
140
+
141
+ float contrast_mod = (0.25*CONTRAST + 0.5*SPIN_AMOUNT + 1.2);
142
+ float paint_res = min(2.0, max(0.0, length(uv)*(0.035)*contrast_mod));
143
+ float c1p = max(0.0, 1.0 - contrast_mod*abs(1.0 - paint_res));
144
+ float c2p = max(0.0, 1.0 - contrast_mod*abs(paint_res));
145
+ float c3p = 1.0 - min(1.0, c1p + c2p);
146
+ float light = (LIGTHING - 0.2)*max(c1p*5.0 - 4.0, 0.0) + LIGTHING*max(c2p*5.0 - 4.0, 0.0);
147
+
148
+ vec4 finalCol = (0.3/CONTRAST)*COLOUR_1 + (1.0 - 0.3/CONTRAST)*(COLOUR_1*c1p + COLOUR_2*c2p + vec4(c3p*COLOUR_3.rgb, c3p*COLOUR_1.a)) + light;
149
+
150
+ gl_FragColor = finalCol;
151
+ }
152
152
  `;
153
153
  const createShader = (type, source) => {
154
154
  const shader = gl.createShader(type);