@stevejtrettel/shader-sandbox 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.
Files changed (106) hide show
  1. package/README.md +391 -0
  2. package/bin/cli.js +389 -0
  3. package/dist-lib/app/App.d.ts +134 -0
  4. package/dist-lib/app/App.d.ts.map +1 -0
  5. package/dist-lib/app/App.js +570 -0
  6. package/dist-lib/app/types.d.ts +32 -0
  7. package/dist-lib/app/types.d.ts.map +1 -0
  8. package/dist-lib/app/types.js +6 -0
  9. package/dist-lib/editor/EditorPanel.d.ts +39 -0
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -0
  11. package/dist-lib/editor/EditorPanel.js +274 -0
  12. package/dist-lib/editor/prism-editor.css +99 -0
  13. package/dist-lib/editor/prism-editor.d.ts +19 -0
  14. package/dist-lib/editor/prism-editor.d.ts.map +1 -0
  15. package/dist-lib/editor/prism-editor.js +96 -0
  16. package/dist-lib/embed.d.ts +17 -0
  17. package/dist-lib/embed.d.ts.map +1 -0
  18. package/dist-lib/embed.js +35 -0
  19. package/dist-lib/engine/ShadertoyEngine.d.ts +160 -0
  20. package/dist-lib/engine/ShadertoyEngine.d.ts.map +1 -0
  21. package/dist-lib/engine/ShadertoyEngine.js +704 -0
  22. package/dist-lib/engine/glHelpers.d.ts +79 -0
  23. package/dist-lib/engine/glHelpers.d.ts.map +1 -0
  24. package/dist-lib/engine/glHelpers.js +298 -0
  25. package/dist-lib/engine/types.d.ts +77 -0
  26. package/dist-lib/engine/types.d.ts.map +1 -0
  27. package/dist-lib/engine/types.js +7 -0
  28. package/dist-lib/index.d.ts +12 -0
  29. package/dist-lib/index.d.ts.map +1 -0
  30. package/dist-lib/index.js +9 -0
  31. package/dist-lib/layouts/DefaultLayout.d.ts +17 -0
  32. package/dist-lib/layouts/DefaultLayout.d.ts.map +1 -0
  33. package/dist-lib/layouts/DefaultLayout.js +27 -0
  34. package/dist-lib/layouts/FullscreenLayout.d.ts +17 -0
  35. package/dist-lib/layouts/FullscreenLayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/FullscreenLayout.js +27 -0
  37. package/dist-lib/layouts/SplitLayout.d.ts +26 -0
  38. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -0
  39. package/dist-lib/layouts/SplitLayout.js +61 -0
  40. package/dist-lib/layouts/TabbedLayout.d.ts +38 -0
  41. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -0
  42. package/dist-lib/layouts/TabbedLayout.js +305 -0
  43. package/dist-lib/layouts/index.d.ts +24 -0
  44. package/dist-lib/layouts/index.d.ts.map +1 -0
  45. package/dist-lib/layouts/index.js +36 -0
  46. package/dist-lib/layouts/split.css +196 -0
  47. package/dist-lib/layouts/tabbed.css +345 -0
  48. package/dist-lib/layouts/types.d.ts +48 -0
  49. package/dist-lib/layouts/types.d.ts.map +1 -0
  50. package/dist-lib/layouts/types.js +4 -0
  51. package/dist-lib/main.d.ts +15 -0
  52. package/dist-lib/main.d.ts.map +1 -0
  53. package/dist-lib/main.js +102 -0
  54. package/dist-lib/project/generatedLoader.d.ts +3 -0
  55. package/dist-lib/project/generatedLoader.d.ts.map +1 -0
  56. package/dist-lib/project/generatedLoader.js +17 -0
  57. package/dist-lib/project/loadProject.d.ts +22 -0
  58. package/dist-lib/project/loadProject.d.ts.map +1 -0
  59. package/dist-lib/project/loadProject.js +350 -0
  60. package/dist-lib/project/loaderHelper.d.ts +7 -0
  61. package/dist-lib/project/loaderHelper.d.ts.map +1 -0
  62. package/dist-lib/project/loaderHelper.js +240 -0
  63. package/dist-lib/project/types.d.ts +192 -0
  64. package/dist-lib/project/types.d.ts.map +1 -0
  65. package/dist-lib/project/types.js +7 -0
  66. package/dist-lib/styles/base.css +29 -0
  67. package/package.json +48 -0
  68. package/src/app/App.ts +699 -0
  69. package/src/app/app.css +208 -0
  70. package/src/app/types.ts +36 -0
  71. package/src/editor/EditorPanel.ts +340 -0
  72. package/src/editor/editor-panel.css +175 -0
  73. package/src/editor/prism-editor.css +99 -0
  74. package/src/editor/prism-editor.ts +124 -0
  75. package/src/embed.ts +55 -0
  76. package/src/engine/ShadertoyEngine.ts +929 -0
  77. package/src/engine/glHelpers.ts +432 -0
  78. package/src/engine/types.ts +118 -0
  79. package/src/index.ts +13 -0
  80. package/src/layouts/DefaultLayout.ts +40 -0
  81. package/src/layouts/FullscreenLayout.ts +40 -0
  82. package/src/layouts/SplitLayout.ts +81 -0
  83. package/src/layouts/TabbedLayout.ts +371 -0
  84. package/src/layouts/default.css +22 -0
  85. package/src/layouts/fullscreen.css +15 -0
  86. package/src/layouts/index.ts +44 -0
  87. package/src/layouts/split.css +196 -0
  88. package/src/layouts/tabbed.css +345 -0
  89. package/src/layouts/types.ts +58 -0
  90. package/src/main.ts +114 -0
  91. package/src/project/generatedLoader.ts +23 -0
  92. package/src/project/loadProject.ts +421 -0
  93. package/src/project/loaderHelper.ts +300 -0
  94. package/src/project/types.ts +243 -0
  95. package/src/styles/base.css +29 -0
  96. package/src/styles/embed.css +14 -0
  97. package/src/vite-env.d.ts +1 -0
  98. package/templates/index.html +28 -0
  99. package/templates/main.ts +126 -0
  100. package/templates/package.json +12 -0
  101. package/templates/shaders/example-buffer/bufferA.glsl +14 -0
  102. package/templates/shaders/example-buffer/config.json +10 -0
  103. package/templates/shaders/example-buffer/image.glsl +5 -0
  104. package/templates/shaders/example-gradient/config.json +4 -0
  105. package/templates/shaders/example-gradient/image.glsl +7 -0
  106. package/templates/vite.config.js +35 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Editor Panel Styles
3
+ */
4
+
5
+ /* Toolbar with tabs and recompile button */
6
+ .editor-toolbar {
7
+ display: flex;
8
+ align-items: center;
9
+ background: #f8f8f8;
10
+ border-bottom: 1px solid #e0e0e0;
11
+ padding-right: 8px;
12
+ }
13
+
14
+ .editor-tab-bar {
15
+ display: flex;
16
+ flex: 1;
17
+ overflow-x: auto;
18
+ scrollbar-width: thin;
19
+ }
20
+
21
+ .editor-tab-button {
22
+ background: transparent;
23
+ border: none;
24
+ color: #666;
25
+ padding: 10px 16px;
26
+ cursor: pointer;
27
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
28
+ font-size: 12px;
29
+ white-space: nowrap;
30
+ border-bottom: 2px solid transparent;
31
+ transition: color 0.15s, border-color 0.15s;
32
+ }
33
+
34
+ .editor-tab-button:hover {
35
+ color: #333;
36
+ }
37
+
38
+ .editor-tab-button.active {
39
+ color: #000;
40
+ border-bottom-color: #4a9eff;
41
+ }
42
+
43
+ .editor-tab-button.image-tab {
44
+ color: #7c4dff;
45
+ }
46
+
47
+ .editor-tab-button.image-tab.active {
48
+ color: #7c4dff;
49
+ border-bottom-color: #7c4dff;
50
+ }
51
+
52
+ /* Copy button (icon only) */
53
+ .editor-copy-button {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ background: transparent;
58
+ border: 1px solid #ccc;
59
+ color: #666;
60
+ width: 32px;
61
+ height: 32px;
62
+ border-radius: 4px;
63
+ cursor: pointer;
64
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
65
+ flex-shrink: 0;
66
+ margin-right: 6px;
67
+ }
68
+
69
+ .editor-copy-button:hover {
70
+ background: #f0f0f0;
71
+ border-color: #999;
72
+ color: #333;
73
+ }
74
+
75
+ .editor-copy-button:active {
76
+ background: #e0e0e0;
77
+ }
78
+
79
+ .editor-copy-button.copied {
80
+ background: #e8f5e9;
81
+ border-color: #4caf50;
82
+ color: #4caf50;
83
+ }
84
+
85
+ .editor-copy-button svg {
86
+ flex-shrink: 0;
87
+ }
88
+
89
+ /* Recompile button */
90
+ .editor-recompile-button {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 6px;
94
+ background: #4a9eff;
95
+ border: none;
96
+ color: #fff;
97
+ padding: 6px 12px;
98
+ border-radius: 4px;
99
+ cursor: pointer;
100
+ font-family: inherit;
101
+ font-size: 12px;
102
+ font-weight: 500;
103
+ transition: background 0.15s;
104
+ flex-shrink: 0;
105
+ }
106
+
107
+ .editor-recompile-button:hover {
108
+ background: #3a8eef;
109
+ }
110
+
111
+ .editor-recompile-button:active {
112
+ background: #2a7edf;
113
+ }
114
+
115
+ .editor-recompile-button svg {
116
+ flex-shrink: 0;
117
+ }
118
+
119
+ /* Content area */
120
+ .editor-content-area {
121
+ flex: 1;
122
+ overflow: hidden;
123
+ position: relative;
124
+ background: #ffffff;
125
+ }
126
+
127
+ .editor-prism-container {
128
+ height: 100%;
129
+ width: 100%;
130
+ }
131
+
132
+ /* Fallback textarea */
133
+ .editor-fallback-textarea {
134
+ width: 100%;
135
+ height: 100%;
136
+ background: #ffffff;
137
+ color: #000;
138
+ border: none;
139
+ padding: 12px;
140
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
141
+ font-size: 13px;
142
+ resize: none;
143
+ outline: none;
144
+ }
145
+
146
+ /* Image viewer */
147
+ .editor-image-viewer {
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ height: 100%;
152
+ background: #f5f5f5;
153
+ padding: 20px;
154
+ }
155
+
156
+ .editor-image-viewer img {
157
+ max-width: 100%;
158
+ max-height: 100%;
159
+ object-fit: contain;
160
+ border-radius: 4px;
161
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
162
+ }
163
+
164
+ /* Error display */
165
+ .editor-error-display {
166
+ background: #fff0f0;
167
+ color: #c00;
168
+ padding: 10px 14px;
169
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
170
+ font-size: 12px;
171
+ white-space: pre-wrap;
172
+ overflow: auto;
173
+ max-height: 120px;
174
+ border-top: 1px solid #fcc;
175
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Prism Editor Styles
3
+ * Textarea + syntax highlighting overlay
4
+ */
5
+
6
+ .prism-editor-wrapper {
7
+ display: flex;
8
+ height: 100%;
9
+ background: #ffffff;
10
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
11
+ font-size: 13px;
12
+ line-height: 1.6;
13
+ }
14
+
15
+ /* Line numbers column */
16
+ .prism-editor-line-numbers {
17
+ flex-shrink: 0;
18
+ padding: 16px 12px 16px 16px;
19
+ text-align: right;
20
+ color: #999;
21
+ border-right: 1px solid #e0e0e0;
22
+ user-select: none;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .prism-editor-line-numbers span {
27
+ display: block;
28
+ }
29
+
30
+ /* Editor area containing textarea and highlight overlay */
31
+ .prism-editor-area {
32
+ flex: 1;
33
+ position: relative;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* Shared styles for textarea and highlight */
38
+ .prism-editor-textarea,
39
+ .prism-editor-highlight {
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ padding: 16px;
46
+ margin: 0;
47
+ border: none;
48
+ outline: none;
49
+ font-family: inherit;
50
+ font-size: inherit;
51
+ line-height: inherit;
52
+ white-space: pre-wrap;
53
+ word-wrap: break-word;
54
+ overflow: auto;
55
+ box-sizing: border-box;
56
+ }
57
+
58
+ /* Textarea - user types here */
59
+ .prism-editor-textarea {
60
+ background: transparent;
61
+ color: transparent;
62
+ caret-color: #000;
63
+ resize: none;
64
+ z-index: 1;
65
+ -webkit-text-fill-color: transparent;
66
+ }
67
+
68
+ .prism-editor-textarea::selection {
69
+ background: rgba(173, 214, 255, 0.4);
70
+ }
71
+
72
+ .prism-editor-textarea::-moz-selection {
73
+ background: rgba(173, 214, 255, 0.4);
74
+ }
75
+
76
+ /* Highlighted code overlay */
77
+ .prism-editor-highlight {
78
+ background: #ffffff;
79
+ color: #000;
80
+ pointer-events: none;
81
+ z-index: 0;
82
+ }
83
+
84
+ .prism-editor-highlight code {
85
+ font-family: inherit;
86
+ font-size: inherit;
87
+ background: none;
88
+ padding: 0;
89
+ }
90
+
91
+ /* Prism syntax highlighting colors */
92
+ .prism-editor-highlight .token.comment { color: #6a9955; }
93
+ .prism-editor-highlight .token.keyword { color: #0000ff; }
94
+ .prism-editor-highlight .token.string { color: #a31515; }
95
+ .prism-editor-highlight .token.number { color: #098658; }
96
+ .prism-editor-highlight .token.operator { color: #000000; }
97
+ .prism-editor-highlight .token.function { color: #795e26; }
98
+ .prism-editor-highlight .token.class-name { color: #267f99; }
99
+ .prism-editor-highlight .token.punctuation { color: #000000; }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Lightweight Prism Editor
3
+ *
4
+ * Simple textarea with Prism syntax highlighting overlay.
5
+ * Uses the existing Prism.js dependency - adds ~0 extra bytes to bundle.
6
+ */
7
+
8
+ import * as Prism from 'prismjs';
9
+ import 'prismjs/components/prism-c';
10
+ import 'prismjs/components/prism-cpp';
11
+
12
+ import './prism-editor.css';
13
+
14
+ export interface EditorInstance {
15
+ getSource: () => string;
16
+ setSource: (source: string) => void;
17
+ destroy: () => void;
18
+ }
19
+
20
+ /**
21
+ * Create a lightweight editor with Prism syntax highlighting.
22
+ */
23
+ export function createEditor(
24
+ container: HTMLElement,
25
+ initialSource: string,
26
+ onChange?: (source: string) => void
27
+ ): EditorInstance {
28
+ // Create wrapper
29
+ const wrapper = document.createElement('div');
30
+ wrapper.className = 'prism-editor-wrapper';
31
+
32
+ // Create line numbers
33
+ const lineNumbers = document.createElement('div');
34
+ lineNumbers.className = 'prism-editor-line-numbers';
35
+
36
+ // Create editor area (textarea + highlighted overlay)
37
+ const editorArea = document.createElement('div');
38
+ editorArea.className = 'prism-editor-area';
39
+
40
+ // Create textarea (user types here)
41
+ const textarea = document.createElement('textarea');
42
+ textarea.className = 'prism-editor-textarea';
43
+ textarea.value = initialSource;
44
+ textarea.spellcheck = false;
45
+ textarea.autocapitalize = 'off';
46
+ textarea.autocomplete = 'off';
47
+
48
+ // Create highlighted code overlay
49
+ const highlight = document.createElement('pre');
50
+ highlight.className = 'prism-editor-highlight';
51
+ const code = document.createElement('code');
52
+ code.className = 'language-cpp';
53
+ highlight.appendChild(code);
54
+
55
+ // Assemble
56
+ editorArea.appendChild(textarea);
57
+ editorArea.appendChild(highlight);
58
+ wrapper.appendChild(lineNumbers);
59
+ wrapper.appendChild(editorArea);
60
+ container.appendChild(wrapper);
61
+
62
+ // Update highlighting and line numbers
63
+ function update() {
64
+ const source = textarea.value;
65
+
66
+ // Update highlighted code
67
+ // Add a trailing newline to prevent layout shift when typing at end
68
+ code.textContent = source + '\n';
69
+ Prism.highlightElement(code);
70
+
71
+ // Update line numbers
72
+ const lines = source.split('\n');
73
+ lineNumbers.innerHTML = lines.map((_, i) => `<span>${i + 1}</span>`).join('');
74
+
75
+ // Notify listener
76
+ if (onChange) {
77
+ onChange(source);
78
+ }
79
+ }
80
+
81
+ // Sync scroll position
82
+ function syncScroll() {
83
+ highlight.scrollTop = textarea.scrollTop;
84
+ highlight.scrollLeft = textarea.scrollLeft;
85
+ lineNumbers.scrollTop = textarea.scrollTop;
86
+ }
87
+
88
+ // Handle tab key for indentation
89
+ function handleKeydown(e: KeyboardEvent) {
90
+ if (e.key === 'Tab') {
91
+ e.preventDefault();
92
+ const start = textarea.selectionStart;
93
+ const end = textarea.selectionEnd;
94
+ const value = textarea.value;
95
+
96
+ // Insert 2 spaces
97
+ textarea.value = value.substring(0, start) + ' ' + value.substring(end);
98
+ textarea.selectionStart = textarea.selectionEnd = start + 2;
99
+ update();
100
+ }
101
+ }
102
+
103
+ // Set up event listeners
104
+ textarea.addEventListener('input', update);
105
+ textarea.addEventListener('scroll', syncScroll);
106
+ textarea.addEventListener('keydown', handleKeydown);
107
+
108
+ // Initial render
109
+ update();
110
+
111
+ return {
112
+ getSource: () => textarea.value,
113
+ setSource: (source: string) => {
114
+ textarea.value = source;
115
+ update();
116
+ },
117
+ destroy: () => {
118
+ textarea.removeEventListener('input', update);
119
+ textarea.removeEventListener('scroll', syncScroll);
120
+ textarea.removeEventListener('keydown', handleKeydown);
121
+ container.removeChild(wrapper);
122
+ },
123
+ };
124
+ }
package/src/embed.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Embeddable Entry Point
3
+ */
4
+
5
+ import './styles/embed.css'; // <-- changed from base.css
6
+
7
+ import { App } from './app/App';
8
+ import { createLayout } from './layouts';
9
+ import { loadDemoProject, DEMO_NAME } from './project/generatedLoader';
10
+
11
+ export interface EmbedOptions {
12
+ container: HTMLElement | string;
13
+ pixelRatio?: number;
14
+ }
15
+
16
+ export interface EmbedResult {
17
+ app: App;
18
+ destroy: () => void;
19
+ }
20
+
21
+ export async function embed(options: EmbedOptions): Promise<EmbedResult> {
22
+ const container = typeof options.container === 'string'
23
+ ? document.querySelector(options.container)
24
+ : options.container;
25
+
26
+ if (!container || !(container instanceof HTMLElement)) {
27
+ throw new Error(`Container not found: ${options.container}`);
28
+ }
29
+
30
+ const project = await loadDemoProject();
31
+
32
+ const layout = createLayout(project.layout, {
33
+ container,
34
+ project,
35
+ });
36
+
37
+ const app = new App({
38
+ container: layout.getCanvasContainer(),
39
+ project,
40
+ pixelRatio: options.pixelRatio ?? window.devicePixelRatio,
41
+ });
42
+
43
+ if (!app.hasErrors()) {
44
+ app.start();
45
+ }
46
+
47
+ return {
48
+ app,
49
+ destroy: () => {
50
+ app.stop?.();
51
+ },
52
+ };
53
+ }
54
+
55
+ export { DEMO_NAME };