@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.
- package/README.md +391 -0
- package/bin/cli.js +389 -0
- package/dist-lib/app/App.d.ts +134 -0
- package/dist-lib/app/App.d.ts.map +1 -0
- package/dist-lib/app/App.js +570 -0
- package/dist-lib/app/types.d.ts +32 -0
- package/dist-lib/app/types.d.ts.map +1 -0
- package/dist-lib/app/types.js +6 -0
- package/dist-lib/editor/EditorPanel.d.ts +39 -0
- package/dist-lib/editor/EditorPanel.d.ts.map +1 -0
- package/dist-lib/editor/EditorPanel.js +274 -0
- package/dist-lib/editor/prism-editor.css +99 -0
- package/dist-lib/editor/prism-editor.d.ts +19 -0
- package/dist-lib/editor/prism-editor.d.ts.map +1 -0
- package/dist-lib/editor/prism-editor.js +96 -0
- package/dist-lib/embed.d.ts +17 -0
- package/dist-lib/embed.d.ts.map +1 -0
- package/dist-lib/embed.js +35 -0
- package/dist-lib/engine/ShadertoyEngine.d.ts +160 -0
- package/dist-lib/engine/ShadertoyEngine.d.ts.map +1 -0
- package/dist-lib/engine/ShadertoyEngine.js +704 -0
- package/dist-lib/engine/glHelpers.d.ts +79 -0
- package/dist-lib/engine/glHelpers.d.ts.map +1 -0
- package/dist-lib/engine/glHelpers.js +298 -0
- package/dist-lib/engine/types.d.ts +77 -0
- package/dist-lib/engine/types.d.ts.map +1 -0
- package/dist-lib/engine/types.js +7 -0
- package/dist-lib/index.d.ts +12 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +9 -0
- package/dist-lib/layouts/DefaultLayout.d.ts +17 -0
- package/dist-lib/layouts/DefaultLayout.d.ts.map +1 -0
- package/dist-lib/layouts/DefaultLayout.js +27 -0
- package/dist-lib/layouts/FullscreenLayout.d.ts +17 -0
- package/dist-lib/layouts/FullscreenLayout.d.ts.map +1 -0
- package/dist-lib/layouts/FullscreenLayout.js +27 -0
- package/dist-lib/layouts/SplitLayout.d.ts +26 -0
- package/dist-lib/layouts/SplitLayout.d.ts.map +1 -0
- package/dist-lib/layouts/SplitLayout.js +61 -0
- package/dist-lib/layouts/TabbedLayout.d.ts +38 -0
- package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -0
- package/dist-lib/layouts/TabbedLayout.js +305 -0
- package/dist-lib/layouts/index.d.ts +24 -0
- package/dist-lib/layouts/index.d.ts.map +1 -0
- package/dist-lib/layouts/index.js +36 -0
- package/dist-lib/layouts/split.css +196 -0
- package/dist-lib/layouts/tabbed.css +345 -0
- package/dist-lib/layouts/types.d.ts +48 -0
- package/dist-lib/layouts/types.d.ts.map +1 -0
- package/dist-lib/layouts/types.js +4 -0
- package/dist-lib/main.d.ts +15 -0
- package/dist-lib/main.d.ts.map +1 -0
- package/dist-lib/main.js +102 -0
- package/dist-lib/project/generatedLoader.d.ts +3 -0
- package/dist-lib/project/generatedLoader.d.ts.map +1 -0
- package/dist-lib/project/generatedLoader.js +17 -0
- package/dist-lib/project/loadProject.d.ts +22 -0
- package/dist-lib/project/loadProject.d.ts.map +1 -0
- package/dist-lib/project/loadProject.js +350 -0
- package/dist-lib/project/loaderHelper.d.ts +7 -0
- package/dist-lib/project/loaderHelper.d.ts.map +1 -0
- package/dist-lib/project/loaderHelper.js +240 -0
- package/dist-lib/project/types.d.ts +192 -0
- package/dist-lib/project/types.d.ts.map +1 -0
- package/dist-lib/project/types.js +7 -0
- package/dist-lib/styles/base.css +29 -0
- package/package.json +48 -0
- package/src/app/App.ts +699 -0
- package/src/app/app.css +208 -0
- package/src/app/types.ts +36 -0
- package/src/editor/EditorPanel.ts +340 -0
- package/src/editor/editor-panel.css +175 -0
- package/src/editor/prism-editor.css +99 -0
- package/src/editor/prism-editor.ts +124 -0
- package/src/embed.ts +55 -0
- package/src/engine/ShadertoyEngine.ts +929 -0
- package/src/engine/glHelpers.ts +432 -0
- package/src/engine/types.ts +118 -0
- package/src/index.ts +13 -0
- package/src/layouts/DefaultLayout.ts +40 -0
- package/src/layouts/FullscreenLayout.ts +40 -0
- package/src/layouts/SplitLayout.ts +81 -0
- package/src/layouts/TabbedLayout.ts +371 -0
- package/src/layouts/default.css +22 -0
- package/src/layouts/fullscreen.css +15 -0
- package/src/layouts/index.ts +44 -0
- package/src/layouts/split.css +196 -0
- package/src/layouts/tabbed.css +345 -0
- package/src/layouts/types.ts +58 -0
- package/src/main.ts +114 -0
- package/src/project/generatedLoader.ts +23 -0
- package/src/project/loadProject.ts +421 -0
- package/src/project/loaderHelper.ts +300 -0
- package/src/project/types.ts +243 -0
- package/src/styles/base.css +29 -0
- package/src/styles/embed.css +14 -0
- package/src/vite-env.d.ts +1 -0
- package/templates/index.html +28 -0
- package/templates/main.ts +126 -0
- package/templates/package.json +12 -0
- package/templates/shaders/example-buffer/bufferA.glsl +14 -0
- package/templates/shaders/example-buffer/config.json +10 -0
- package/templates/shaders/example-buffer/image.glsl +5 -0
- package/templates/shaders/example-gradient/config.json +4 -0
- package/templates/shaders/example-gradient/image.glsl +7 -0
- package/templates/vite.config.js +35 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split Layout Styles
|
|
3
|
+
* Includes code panel, tabs, and Prism.js syntax highlighting theme
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* ===== Split Layout ===== */
|
|
7
|
+
.layout-split {
|
|
8
|
+
width: 100%;
|
|
9
|
+
height: 100%;
|
|
10
|
+
display: flex;
|
|
11
|
+
gap: 40px;
|
|
12
|
+
/* Very generous margins on large screens */
|
|
13
|
+
padding: 120px 140px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.layout-split .canvas-container {
|
|
17
|
+
position: relative;
|
|
18
|
+
flex: 1;
|
|
19
|
+
background: #000;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2), 0 3px 8px rgba(0, 0, 0, 0.12);
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.layout-split .code-panel {
|
|
26
|
+
position: relative;
|
|
27
|
+
flex: 1;
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
background: white;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2), 0 3px 8px rgba(0, 0, 0, 0.12);
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ===== Code Panel ===== */
|
|
37
|
+
.tab-bar {
|
|
38
|
+
display: flex;
|
|
39
|
+
background: #f8f8f8;
|
|
40
|
+
border-bottom: 1px solid #e0e0e0;
|
|
41
|
+
padding: 8px 8px 0 8px;
|
|
42
|
+
gap: 4px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tab-button {
|
|
46
|
+
padding: 8px 16px;
|
|
47
|
+
background: transparent;
|
|
48
|
+
border: none;
|
|
49
|
+
border-radius: 6px 6px 0 0;
|
|
50
|
+
font-size: 13px;
|
|
51
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
transition: background 0.2s;
|
|
54
|
+
color: #666;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.tab-button:hover {
|
|
58
|
+
background: #e8e8e8;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-button.active {
|
|
62
|
+
background: white;
|
|
63
|
+
color: #000;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.copy-button {
|
|
68
|
+
position: absolute;
|
|
69
|
+
top: 12px;
|
|
70
|
+
right: 12px;
|
|
71
|
+
padding: 6px;
|
|
72
|
+
background: transparent;
|
|
73
|
+
border: none;
|
|
74
|
+
border-radius: 4px;
|
|
75
|
+
color: #666;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
transition: all 0.2s;
|
|
78
|
+
z-index: 10;
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.copy-button:hover {
|
|
85
|
+
background: rgba(0, 0, 0, 0.05);
|
|
86
|
+
color: #333;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.copy-button:active {
|
|
90
|
+
transform: scale(0.9);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.copy-button.copied {
|
|
94
|
+
color: #4caf50;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.code-viewer {
|
|
98
|
+
flex: 1;
|
|
99
|
+
min-height: 0; /* Allow shrinking below content size in flexbox */
|
|
100
|
+
overflow: auto;
|
|
101
|
+
position: relative;
|
|
102
|
+
background: white;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Prism.js syntax highlighting */
|
|
106
|
+
.code-viewer pre {
|
|
107
|
+
margin: 0;
|
|
108
|
+
padding: 16px;
|
|
109
|
+
font-size: 13px;
|
|
110
|
+
line-height: 1.5;
|
|
111
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
112
|
+
background: white;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.code-viewer code {
|
|
116
|
+
font-family: inherit;
|
|
117
|
+
font-size: inherit;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Prism theme - simple light theme */
|
|
121
|
+
.token.comment { color: #6a9955; }
|
|
122
|
+
.token.keyword { color: #0000ff; }
|
|
123
|
+
.token.string { color: #a31515; }
|
|
124
|
+
.token.number { color: #098658; }
|
|
125
|
+
.token.operator { color: #000000; }
|
|
126
|
+
.token.function { color: #795e26; }
|
|
127
|
+
.token.class-name { color: #267f99; }
|
|
128
|
+
.token.punctuation { color: #000000; }
|
|
129
|
+
|
|
130
|
+
/* Image tab styling */
|
|
131
|
+
.tab-button.image-tab {
|
|
132
|
+
color: #7c4dff;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.tab-button.image-tab.active {
|
|
136
|
+
color: #7c4dff;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Image viewer */
|
|
140
|
+
.image-viewer {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
height: 100%;
|
|
145
|
+
padding: 16px;
|
|
146
|
+
background: #f5f5f5;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.image-viewer img {
|
|
150
|
+
max-width: 100%;
|
|
151
|
+
max-height: 100%;
|
|
152
|
+
object-fit: contain;
|
|
153
|
+
border-radius: 4px;
|
|
154
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ===== Responsive adjustments ===== */
|
|
158
|
+
|
|
159
|
+
/* Progressively shrink margins on smaller screens */
|
|
160
|
+
@media (max-width: 1800px) {
|
|
161
|
+
.layout-split {
|
|
162
|
+
padding: 100px 120px;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@media (max-width: 1600px) {
|
|
167
|
+
.layout-split {
|
|
168
|
+
padding: 80px 100px;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@media (max-width: 1400px) {
|
|
173
|
+
.layout-split {
|
|
174
|
+
padding: 60px 80px;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@media (max-width: 1200px) {
|
|
179
|
+
.layout-split {
|
|
180
|
+
padding: 50px 60px;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@media (max-width: 1000px) {
|
|
185
|
+
.layout-split {
|
|
186
|
+
padding: 40px 50px;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@media (max-width: 800px) {
|
|
191
|
+
.layout-split {
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
padding: 30px;
|
|
194
|
+
gap: 30px;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabbed Layout Styles
|
|
3
|
+
* Single window with tabs switching between shader and code views
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* ===== Theme Variables (for code viewer) ===== */
|
|
7
|
+
.layout-tabbed {
|
|
8
|
+
--tab-border: #e0e0e0;
|
|
9
|
+
--code-bg: white;
|
|
10
|
+
--code-text: #000;
|
|
11
|
+
--line-number-text: #999;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.layout-tabbed {
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 100%;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: 60px;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.tabbed-wrapper {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
width: 800px;
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
height: 650px; /* 600px content + ~50px tabs */
|
|
31
|
+
max-height: 100%;
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25), 0 5px 15px rgba(0, 0, 0, 0.15);
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ===== Toolbar (contains tabs + buttons) ===== */
|
|
38
|
+
.tabbed-toolbar {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
background: #f8f8f8;
|
|
43
|
+
border-bottom: 1px solid #e0e0e0;
|
|
44
|
+
padding-right: 8px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ===== Tab Bar ===== */
|
|
48
|
+
.tabbed-tab-bar {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex: 1;
|
|
51
|
+
gap: 4px;
|
|
52
|
+
overflow-x: auto;
|
|
53
|
+
overflow-y: hidden;
|
|
54
|
+
scrollbar-width: thin;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Hide scrollbar on webkit but keep functionality */
|
|
58
|
+
.tabbed-tab-bar::-webkit-scrollbar {
|
|
59
|
+
height: 4px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.tabbed-tab-bar::-webkit-scrollbar-thumb {
|
|
63
|
+
background: #ccc;
|
|
64
|
+
border-radius: 2px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tabbed-tab-button {
|
|
68
|
+
padding: 10px 16px;
|
|
69
|
+
background: transparent;
|
|
70
|
+
border: none;
|
|
71
|
+
border-bottom: 2px solid transparent;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
transition: color 0.15s, border-color 0.15s;
|
|
76
|
+
color: #666;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.tabbed-tab-button:hover {
|
|
82
|
+
color: #333;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tabbed-tab-button.active {
|
|
86
|
+
color: #000;
|
|
87
|
+
border-bottom-color: #4a9eff;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tabbed-tab-button.shader-tab {
|
|
91
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.tabbed-tab-button.image-tab {
|
|
95
|
+
color: #7c4dff;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.tabbed-tab-button.image-tab.active {
|
|
99
|
+
color: #7c4dff;
|
|
100
|
+
border-bottom-color: #7c4dff;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ===== Content Area ===== */
|
|
104
|
+
.tabbed-content {
|
|
105
|
+
flex: 1;
|
|
106
|
+
min-height: 0; /* Critical for flexbox scrolling */
|
|
107
|
+
position: relative;
|
|
108
|
+
background: #000;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.tabbed-canvas-container {
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: 0;
|
|
115
|
+
left: 0;
|
|
116
|
+
width: 100%;
|
|
117
|
+
height: 100%;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ===== Code Viewer ===== */
|
|
121
|
+
.tabbed-code-viewer {
|
|
122
|
+
position: absolute;
|
|
123
|
+
top: 0;
|
|
124
|
+
left: 0;
|
|
125
|
+
width: 100%;
|
|
126
|
+
height: 100%;
|
|
127
|
+
overflow: auto;
|
|
128
|
+
background: var(--code-bg);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.tabbed-code-viewer pre {
|
|
132
|
+
margin: 0;
|
|
133
|
+
padding: 16px 16px 16px 0;
|
|
134
|
+
font-size: 13px;
|
|
135
|
+
line-height: 1.6;
|
|
136
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
137
|
+
background: var(--code-bg);
|
|
138
|
+
color: var(--code-text);
|
|
139
|
+
display: flex;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.tabbed-code-viewer code {
|
|
143
|
+
font-family: inherit;
|
|
144
|
+
font-size: inherit;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Line numbers */
|
|
148
|
+
.tabbed-line-numbers {
|
|
149
|
+
text-align: right;
|
|
150
|
+
padding-right: 16px;
|
|
151
|
+
margin-right: 16px;
|
|
152
|
+
border-right: 1px solid var(--tab-border);
|
|
153
|
+
color: var(--line-number-text);
|
|
154
|
+
user-select: none;
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
padding-left: 16px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tabbed-code-content {
|
|
160
|
+
flex: 1;
|
|
161
|
+
overflow-x: auto;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ===== Image Viewer ===== */
|
|
165
|
+
.tabbed-image-viewer {
|
|
166
|
+
position: absolute;
|
|
167
|
+
top: 0;
|
|
168
|
+
left: 0;
|
|
169
|
+
width: 100%;
|
|
170
|
+
height: 100%;
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
background: #f5f5f5;
|
|
175
|
+
padding: 20px;
|
|
176
|
+
box-sizing: border-box;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.tabbed-image-viewer img {
|
|
180
|
+
max-width: 100%;
|
|
181
|
+
max-height: 100%;
|
|
182
|
+
object-fit: contain;
|
|
183
|
+
border-radius: 4px;
|
|
184
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ===== Prism Theme (Light) ===== */
|
|
188
|
+
.tabbed-code-viewer .token.comment { color: #6a9955; }
|
|
189
|
+
.tabbed-code-viewer .token.keyword { color: #0000ff; }
|
|
190
|
+
.tabbed-code-viewer .token.string { color: #a31515; }
|
|
191
|
+
.tabbed-code-viewer .token.number { color: #098658; }
|
|
192
|
+
.tabbed-code-viewer .token.operator { color: #000000; }
|
|
193
|
+
.tabbed-code-viewer .token.function { color: #795e26; }
|
|
194
|
+
.tabbed-code-viewer .token.class-name { color: #267f99; }
|
|
195
|
+
.tabbed-code-viewer .token.punctuation { color: #000000; }
|
|
196
|
+
|
|
197
|
+
/* ===== Prism Theme (Dark) ===== */
|
|
198
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.comment,
|
|
199
|
+
.dark .tabbed-code-viewer .token.comment { color: #6a9955; }
|
|
200
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.keyword,
|
|
201
|
+
.dark .tabbed-code-viewer .token.keyword { color: #569cd6; }
|
|
202
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.string,
|
|
203
|
+
.dark .tabbed-code-viewer .token.string { color: #ce9178; }
|
|
204
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.number,
|
|
205
|
+
.dark .tabbed-code-viewer .token.number { color: #b5cea8; }
|
|
206
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.operator,
|
|
207
|
+
.dark .tabbed-code-viewer .token.operator { color: #d4d4d4; }
|
|
208
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.function,
|
|
209
|
+
.dark .tabbed-code-viewer .token.function { color: #dcdcaa; }
|
|
210
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.class-name,
|
|
211
|
+
.dark .tabbed-code-viewer .token.class-name { color: #4ec9b0; }
|
|
212
|
+
[data-bs-theme="dark"] .tabbed-code-viewer .token.punctuation,
|
|
213
|
+
.dark .tabbed-code-viewer .token.punctuation { color: #d4d4d4; }
|
|
214
|
+
|
|
215
|
+
/* ===== Responsive ===== */
|
|
216
|
+
@media (max-width: 1200px) {
|
|
217
|
+
.layout-tabbed {
|
|
218
|
+
padding: 40px 60px;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@media (max-width: 900px) {
|
|
223
|
+
.layout-tabbed {
|
|
224
|
+
padding: 30px 40px;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@media (max-width: 600px) {
|
|
229
|
+
.layout-tabbed {
|
|
230
|
+
padding: 20px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.tabbed-tab-button {
|
|
234
|
+
padding: 8px 12px;
|
|
235
|
+
font-size: 12px;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* ===== Editor Mode ===== */
|
|
240
|
+
.tabbed-editor-container {
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: 0;
|
|
243
|
+
left: 0;
|
|
244
|
+
width: 100%;
|
|
245
|
+
height: 100%;
|
|
246
|
+
overflow: hidden;
|
|
247
|
+
background: #ffffff;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Button container for copy and recompile (in toolbar) */
|
|
251
|
+
.tabbed-button-container {
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: center;
|
|
254
|
+
gap: 6px;
|
|
255
|
+
flex-shrink: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Copy button (icon only) */
|
|
259
|
+
.tabbed-copy-button {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
background: transparent;
|
|
264
|
+
border: 1px solid #ccc;
|
|
265
|
+
color: #666;
|
|
266
|
+
width: 32px;
|
|
267
|
+
height: 32px;
|
|
268
|
+
border-radius: 4px;
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.tabbed-copy-button:hover {
|
|
274
|
+
background: #f0f0f0;
|
|
275
|
+
border-color: #999;
|
|
276
|
+
color: #333;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.tabbed-copy-button:active {
|
|
280
|
+
background: #e0e0e0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.tabbed-copy-button.copied {
|
|
284
|
+
background: #e8f5e9;
|
|
285
|
+
border-color: #4caf50;
|
|
286
|
+
color: #4caf50;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.tabbed-recompile-button {
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 6px;
|
|
293
|
+
background: #4a9eff;
|
|
294
|
+
border: none;
|
|
295
|
+
color: #fff;
|
|
296
|
+
padding: 6px 12px;
|
|
297
|
+
border-radius: 4px;
|
|
298
|
+
cursor: pointer;
|
|
299
|
+
font-family: inherit;
|
|
300
|
+
font-size: 12px;
|
|
301
|
+
font-weight: 500;
|
|
302
|
+
transition: background 0.15s;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.tabbed-recompile-button:hover {
|
|
306
|
+
background: #3a8eef;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.tabbed-recompile-button:active {
|
|
310
|
+
background: #2a7edf;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.tabbed-recompile-button svg {
|
|
314
|
+
flex-shrink: 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.tabbed-error-display {
|
|
318
|
+
position: absolute;
|
|
319
|
+
bottom: 0;
|
|
320
|
+
left: 0;
|
|
321
|
+
right: 0;
|
|
322
|
+
background: #fff0f0;
|
|
323
|
+
color: #c00;
|
|
324
|
+
padding: 10px 14px;
|
|
325
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
326
|
+
font-size: 12px;
|
|
327
|
+
white-space: pre-wrap;
|
|
328
|
+
overflow: auto;
|
|
329
|
+
max-height: 120px;
|
|
330
|
+
border-top: 1px solid #fcc;
|
|
331
|
+
z-index: 10;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.tabbed-fallback-textarea {
|
|
335
|
+
width: 100%;
|
|
336
|
+
height: 100%;
|
|
337
|
+
background: #ffffff;
|
|
338
|
+
color: #000;
|
|
339
|
+
border: none;
|
|
340
|
+
padding: 12px;
|
|
341
|
+
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
|
|
342
|
+
font-size: 13px;
|
|
343
|
+
resize: none;
|
|
344
|
+
outline: none;
|
|
345
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Types - Common interface for all layout modes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ShadertoyProject, PassName } from '../project/types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result of a recompilation attempt.
|
|
9
|
+
*/
|
|
10
|
+
export interface RecompileResult {
|
|
11
|
+
success: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callback for recompiling shader code.
|
|
17
|
+
* @param passName - 'common' for common.glsl, or a PassName for individual passes
|
|
18
|
+
* @param newSource - New GLSL source code
|
|
19
|
+
* @returns Result indicating success or failure with error message
|
|
20
|
+
*/
|
|
21
|
+
export type RecompileHandler = (
|
|
22
|
+
passName: 'common' | PassName,
|
|
23
|
+
newSource: string
|
|
24
|
+
) => RecompileResult;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Base interface that all layouts must implement.
|
|
28
|
+
*/
|
|
29
|
+
export interface BaseLayout {
|
|
30
|
+
/**
|
|
31
|
+
* Get the canvas container element where App should render.
|
|
32
|
+
*/
|
|
33
|
+
getCanvasContainer(): HTMLElement;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set the recompile handler for editor mode.
|
|
37
|
+
* Called by App after initialization to wire up recompilation.
|
|
38
|
+
*/
|
|
39
|
+
setRecompileHandler?(handler: RecompileHandler): void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clean up all DOM elements and resources.
|
|
43
|
+
*/
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Options for creating a layout.
|
|
49
|
+
*/
|
|
50
|
+
export interface LayoutOptions {
|
|
51
|
+
container: HTMLElement;
|
|
52
|
+
project: ShadertoyProject;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Available layout modes.
|
|
57
|
+
*/
|
|
58
|
+
export type LayoutMode = 'fullscreen' | 'default' | 'split' | 'tabbed';
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Loads a demo project from the demos/ folder and starts the App.
|
|
5
|
+
*
|
|
6
|
+
* To run a specific demo:
|
|
7
|
+
* npm run dev:demo <demo-name>
|
|
8
|
+
* npm run build:demo <demo-name>
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* npm run dev:demo keyboard-test
|
|
12
|
+
* npm run build:demo simple-gradient
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import './styles/base.css';
|
|
16
|
+
|
|
17
|
+
import { App } from './app/App';
|
|
18
|
+
import { createLayout } from './layouts';
|
|
19
|
+
import { loadDemoProject, DEMO_NAME } from './project/generatedLoader';
|
|
20
|
+
import { PassName } from './project/types';
|
|
21
|
+
import { RecompileResult } from './layouts/types';
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
try {
|
|
25
|
+
console.log(`Loading demo: ${DEMO_NAME}`);
|
|
26
|
+
|
|
27
|
+
// Load the demo project from demos/ folder
|
|
28
|
+
// The demo is determined by the generated loader (created by dev-demo.cjs or build-demo.cjs)
|
|
29
|
+
const project = await loadDemoProject();
|
|
30
|
+
|
|
31
|
+
console.log(`Loaded project: ${project.meta.title}`);
|
|
32
|
+
console.log(`Passes:`, Object.keys(project.passes).filter(k => project.passes[k as keyof typeof project.passes]));
|
|
33
|
+
|
|
34
|
+
// Get root container element
|
|
35
|
+
const rootContainer = document.getElementById('app');
|
|
36
|
+
if (!rootContainer) {
|
|
37
|
+
throw new Error('Container element #app not found');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create layout
|
|
41
|
+
const layout = createLayout(project.layout, {
|
|
42
|
+
container: rootContainer,
|
|
43
|
+
project,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Get canvas container from layout
|
|
47
|
+
const canvasContainer = layout.getCanvasContainer();
|
|
48
|
+
|
|
49
|
+
// Create app
|
|
50
|
+
const app = new App({
|
|
51
|
+
container: canvasContainer,
|
|
52
|
+
project,
|
|
53
|
+
pixelRatio: window.devicePixelRatio,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Wire up recompile handler for layouts that support it (split, tabbed)
|
|
57
|
+
if (layout.setRecompileHandler) {
|
|
58
|
+
layout.setRecompileHandler((passName: 'common' | PassName, newSource: string): RecompileResult => {
|
|
59
|
+
const engine = app.getEngine();
|
|
60
|
+
if (!engine) {
|
|
61
|
+
return { success: false, error: 'Engine not initialized' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (passName === 'common') {
|
|
65
|
+
const result = engine.recompileCommon(newSource);
|
|
66
|
+
if (result.success) {
|
|
67
|
+
return { success: true };
|
|
68
|
+
} else {
|
|
69
|
+
// Return first error
|
|
70
|
+
const firstError = result.errors[0];
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: firstError ? `${firstError.passName}: ${firstError.error}` : 'Unknown error',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
return engine.recompilePass(passName, newSource);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Only start animation loop if there are no compilation errors
|
|
83
|
+
// If there are errors, the error overlay is already shown by App constructor
|
|
84
|
+
if (!app.hasErrors()) {
|
|
85
|
+
app.start();
|
|
86
|
+
console.log('App started!');
|
|
87
|
+
} else {
|
|
88
|
+
console.warn('App not started due to shader compilation errors');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Expose for debugging
|
|
92
|
+
(window as any).app = app;
|
|
93
|
+
(window as any).layout = layout;
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Failed to initialize:', error);
|
|
97
|
+
const container = document.getElementById('app');
|
|
98
|
+
if (container) {
|
|
99
|
+
container.innerHTML = `
|
|
100
|
+
<div style="color: red; padding: 20px; font-family: monospace;">
|
|
101
|
+
<h2>Error</h2>
|
|
102
|
+
<pre>${error instanceof Error ? error.message : String(error)}</pre>
|
|
103
|
+
</div>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Start when DOM is ready
|
|
110
|
+
if (document.readyState === 'loading') {
|
|
111
|
+
document.addEventListener('DOMContentLoaded', main);
|
|
112
|
+
} else {
|
|
113
|
+
main();
|
|
114
|
+
}
|