fastled 1.2.49__py3-none-any.whl → 1.2.51__py3-none-any.whl

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.
fastled/site/build.py CHANGED
@@ -1,457 +1,457 @@
1
- import argparse
2
- import subprocess
3
- from pathlib import Path
4
- from shutil import copytree, rmtree, which
5
-
6
- CSS_CONTENT = """
7
- /* CSS Reset & Variables */
8
- *, *::before, *::after {
9
- box-sizing: border-box;
10
- margin: 0;
11
- padding: 0;
12
- }
13
-
14
- :root {
15
- --color-background: #121212;
16
- --color-surface: #252525;
17
- --color-surface-transparent: rgba(30, 30, 30, 0.95);
18
- --color-text: #E0E0E0;
19
- --spacing-sm: 5px;
20
- --spacing-md: 10px;
21
- --spacing-lg: 15px;
22
- --transition-speed: 0.3s;
23
- --font-family: 'Roboto Condensed', sans-serif;
24
- --nav-width: 250px;
25
- --border-radius: 5px;
26
- }
27
-
28
- /* Base Styles */
29
- body {
30
- background-color: var(--color-background);
31
- color: var(--color-text);
32
- margin: 0;
33
- padding: 0;
34
- font-family: var(--font-family);
35
- min-height: 100vh;
36
- display: grid;
37
- grid-template-rows: 1fr;
38
- }
39
-
40
- /* Splash Screen */
41
- .splash-screen {
42
- position: fixed;
43
- inset: 0;
44
- background-color: var(--color-background);
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
- z-index: 2000;
49
- transition: opacity var(--transition-speed) ease-out;
50
- }
51
-
52
- .splash-text {
53
- font-size: 14vw;
54
- color: var(--color-text);
55
- font-weight: 300;
56
- font-family: var(--font-family);
57
- opacity: 0;
58
- transition: opacity var(--transition-speed) ease-in;
59
- }
60
-
61
- /* Layout */
62
- .content-wrapper {
63
- position: relative;
64
- width: 100%;
65
- height: 100vh;
66
- overflow-x: hidden;
67
- }
68
-
69
- /* Navigation */
70
- .nav-trigger {
71
- position: fixed;
72
- left: var(--spacing-md);
73
- top: var(--spacing-md);
74
- padding: 15px 30px;
75
- z-index: 1001;
76
- background-color: var(--color-surface);
77
- border-radius: var(--border-radius);
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- cursor: pointer;
82
- color: var(--color-text);
83
- font-size: 24px;
84
- transition: background-color var(--transition-speed) ease;
85
- }
86
-
87
- .nav-trigger:hover {
88
- background-color: var(--color-surface-transparent);
89
- }
90
-
91
- .nav-trigger .fa-chevron-down {
92
- margin-left: 10px;
93
- transition: transform var(--transition-speed) ease;
94
- }
95
-
96
- .nav-pane.visible + .nav-trigger .fa-chevron-down {
97
- transform: rotate(180deg);
98
- }
99
-
100
- .nav-pane {
101
- position: fixed;
102
- left: var(--spacing-md);
103
- top: 60px;
104
- width: var(--nav-width);
105
- height: auto;
106
- background-color: var(--color-surface-transparent);
107
- border-radius: var(--border-radius);
108
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
109
- transform: translateY(-20px);
110
- opacity: 0;
111
- pointer-events: none;
112
- transition: transform var(--transition-speed) ease,
113
- opacity var(--transition-speed) ease;
114
- }
115
-
116
- .nav-pane.visible {
117
- transform: translateY(0);
118
- opacity: 1;
119
- pointer-events: auto;
120
- }
121
-
122
- /* Main Content */
123
- .main-content {
124
- width: 100%;
125
- height: 100%;
126
- padding: 0;
127
- overflow: hidden;
128
- }
129
-
130
- #example-frame {
131
- width: 100%;
132
- height: 100%;
133
- border: none;
134
- background-color: var(--color-background);
135
- overflow: auto;
136
- }
137
-
138
- /* Example Links */
139
- .example-link {
140
- margin: var(--spacing-sm) var(--spacing-md);
141
- padding: var(--spacing-lg) var(--spacing-md);
142
- border-radius: var(--border-radius);
143
- display: block;
144
- text-decoration: none;
145
- color: var(--color-text);
146
- background-color: var(--color-surface);
147
- transition: background-color var(--transition-speed) ease-in-out,
148
- box-shadow var(--transition-speed) ease-in-out;
149
- position: relative;
150
- padding-right: 35px;
151
- }
152
-
153
- .example-link:hover {
154
- background-color: var(--color-surface-transparent);
155
- box-shadow: var(--shadow-hover, 0 0 10px rgba(255, 255, 255, 0.1));
156
- }
157
-
158
- .example-link:last-child {
159
- margin-bottom: var(--spacing-md);
160
- }
161
-
162
- /* Accessibility */
163
- @media (prefers-reduced-motion: reduce) {
164
- *, *::before, *::after {
165
- animation-duration: 0.01ms !important;
166
- animation-iteration-count: 1 !important;
167
- transition-duration: 0.01ms !important;
168
- scroll-behavior: auto !important;
169
- }
170
- }
171
- """
172
-
173
- INDEX_TEMPLATE = """<!DOCTYPE html>
174
- <html lang="en">
175
- <head>
176
- <meta charset="UTF-8">
177
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
178
- <title>FastLED Examples</title>
179
- <link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300&display=swap" rel="stylesheet">
180
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
181
- <link rel="stylesheet" href="index.css">
182
- </head>
183
- <body>
184
- <div class="splash-screen">
185
- <div class="splash-text">FastLED</div>
186
- </div>
187
- <div class="content-wrapper">
188
- <div class="nav-trigger">Examples <i class="fas fa-chevron-down"></i></div>
189
- <nav class="nav-pane">
190
- {example_links}
191
- </nav>
192
- <main class="main-content">
193
- <iframe id="example-frame" title="Example Content"></iframe>
194
- </main>
195
- </div>
196
- <script>
197
- document.addEventListener('DOMContentLoaded', function() {{
198
- const splashScreen = document.querySelector('.splash-screen');
199
- const splashText = document.querySelector('.splash-text');
200
-
201
- // Ensure splash screen always gets removed
202
- const removeSplashScreen = () => {{
203
- splashScreen.style.opacity = '0';
204
- setTimeout(() => {{
205
- splashScreen.style.display = 'none';
206
- }}, 500);
207
- }};
208
-
209
- // Set a maximum time the splash screen can stay
210
- const maxSplashTime = setTimeout(removeSplashScreen, 2000); // Reduced from 5000ms to 2000ms
211
-
212
- // Try to do nice fade-in/fade-out when possible
213
- try {{
214
- // Add a fallback timer in case font loading fails silently
215
- const fontTimeout = setTimeout(() => {{
216
- splashText.style.opacity = '1';
217
- setTimeout(removeSplashScreen, 1000);
218
- }}, 1000);
219
-
220
- Promise.all([
221
- // Wrap font loading in a timeout promise
222
- Promise.race([
223
- document.fonts.ready,
224
- new Promise((_, reject) => setTimeout(reject, 1500))
225
- ]),
226
- new Promise(resolve => {{
227
- if (document.readyState === 'complete') {{
228
- resolve();
229
- }} else {{
230
- window.addEventListener('load', resolve);
231
- }}
232
- }})
233
- ]).then(() => {{
234
- clearTimeout(maxSplashTime);
235
- clearTimeout(fontTimeout);
236
- splashText.style.opacity = '1';
237
- setTimeout(removeSplashScreen, 1500);
238
- }}).catch(() => {{
239
- // If either promise fails, ensure splash screen is removed
240
- clearTimeout(maxSplashTime);
241
- removeSplashScreen();
242
- }});
243
- }} catch (e) {{
244
- // Final fallback if anything goes wrong
245
- console.warn('Splash screen error:', e);
246
- removeSplashScreen();
247
- }}
248
- const links = document.querySelectorAll('.example-link');
249
- const iframe = document.getElementById('example-frame');
250
- const navPane = document.querySelector('.nav-pane');
251
- const navTrigger = document.querySelector('.nav-trigger');
252
-
253
- // First add checkmarks to all links
254
- links.forEach(link => {{
255
- // Add the checkmark span to each link
256
- const checkmark = document.createElement('i');
257
- checkmark.className = 'fas fa-check';
258
- checkmark.style.display = 'none';
259
- checkmark.style.position = 'absolute';
260
- checkmark.style.right = '10px';
261
- checkmark.style.top = '50%';
262
- checkmark.style.transform = 'translateY(-50%)';
263
- checkmark.style.color = '#E0E0E0';
264
- link.appendChild(checkmark);
265
- }});
266
-
267
- // Now load first example and show its checkmark
268
- if (links.length > 0) {{
269
- // Try to find SdCard example first
270
- let startLink = Array.from(links).find(link => link.textContent === 'SdCard') || links[0];
271
- iframe.src = startLink.getAttribute('href');
272
- startLink.classList.add('active');
273
- startLink.querySelector('.fa-check').style.display = 'inline-block';
274
- }}
275
-
276
- // Add click handlers
277
- links.forEach(link => {{
278
- link.addEventListener('click', function(e) {{
279
- e.preventDefault();
280
- // Hide all checkmarks
281
- links.forEach(l => {{
282
- l.querySelector('.fa-check').style.display = 'none';
283
- l.classList.remove('active');
284
- }});
285
- // Show this checkmark
286
- this.querySelector('.fa-check').style.display = 'inline-block';
287
- this.classList.add('active');
288
- iframe.src = this.getAttribute('href');
289
- hideNav(); // Hide nav after selection
290
- }});
291
- }});
292
-
293
- function showNav() {{
294
- navPane.classList.add('visible');
295
- navPane.style.opacity = '1';
296
- }}
297
-
298
- function hideNav() {{
299
- navPane.style.opacity = '0'; // Start fade out
300
- setTimeout(() => {{
301
- navPane.classList.remove('visible');
302
- }}, 300);
303
- }}
304
-
305
- // Click handlers for nav
306
- navTrigger.addEventListener('click', (e) => {{
307
- e.stopPropagation();
308
- if (navPane.classList.contains('visible')) {{
309
- hideNav();
310
- }} else {{
311
- showNav();
312
- }}
313
- }});
314
-
315
- // Close menu when clicking anywhere in the document
316
- document.addEventListener('click', (e) => {{
317
- if (navPane.classList.contains('visible') &&
318
- !navPane.contains(e.target) &&
319
- !navTrigger.contains(e.target)) {{
320
- hideNav();
321
- }}
322
- }});
323
-
324
- // Close when clicking iframe
325
- iframe.addEventListener('load', () => {{
326
- iframe.contentDocument?.addEventListener('click', () => {{
327
- if (navPane.classList.contains('visible')) {{
328
- hideNav();
329
- }}
330
- }});
331
- }});
332
-
333
- // Initial state
334
- hideNav();
335
- }});
336
- </script>
337
- </body>
338
- </html>
339
- """
340
-
341
-
342
- EXAMPLES = [
343
- "wasm",
344
- "Chromancer",
345
- "LuminescentGrand",
346
- "FxSdCard",
347
- "FxNoiseRing",
348
- "FxWater",
349
- ]
350
-
351
-
352
- def _exec(cmd: str) -> None:
353
- subprocess.run(cmd, shell=True, check=True)
354
-
355
-
356
- def build_example(example: str, outputdir: Path) -> None:
357
- if not which("fastled"):
358
- raise FileNotFoundError("fastled executable not found")
359
- src_dir = outputdir / example / "src"
360
- _exec(f"fastled --init={example} {src_dir}")
361
- assert src_dir.exists()
362
- _exec(f"fastled {src_dir / example} --just-compile")
363
- fastled_dir = src_dir / example / "fastled_js"
364
- assert fastled_dir.exists(), f"fastled dir {fastled_dir} not found"
365
- # now copy it to the example dir
366
- example_dir = outputdir / example
367
- copytree(fastled_dir, example_dir, dirs_exist_ok=True)
368
- # now remove the src dir
369
- rmtree(src_dir, ignore_errors=True)
370
- print(f"Built {example} example in {example_dir}")
371
- assert (example_dir / "fastled.wasm").exists()
372
-
373
-
374
- def generate_css(outputdir: Path) -> None:
375
- css_file = outputdir / "index.css"
376
- # with open(css_file, "w") as f:
377
- # f.write(CSS_CONTENT, encoding="utf-8")
378
- css_file.write_text(CSS_CONTENT, encoding="utf-8")
379
-
380
-
381
- def build_index_html(outputdir: Path) -> None:
382
- outputdir = outputdir
383
- assert (
384
- outputdir.exists()
385
- ), f"Output directory {outputdir} not found, you should run build_example first"
386
- index_html = outputdir / "index.html"
387
-
388
- examples = [f for f in outputdir.iterdir() if f.is_dir()]
389
- examples = sorted(examples)
390
-
391
- example_links = "\n".join(
392
- f' <a class="example-link" href="{example.name}/index.html">{example.name}</a>'
393
- for example in examples
394
- )
395
-
396
- with open(index_html, "w") as f:
397
- f.write(INDEX_TEMPLATE.format(example_links=example_links))
398
-
399
-
400
- def parse_args() -> argparse.Namespace:
401
- parser = argparse.ArgumentParser(description="Build FastLED example site")
402
- parser.add_argument(
403
- "--outputdir", type=Path, help="Output directory", required=True
404
- )
405
- parser.add_argument(
406
- "--fast",
407
- action="store_true",
408
- help="Skip regenerating existing examples, only rebuild index.html and CSS",
409
- )
410
- return parser.parse_args()
411
-
412
-
413
- def build(outputdir: Path, fast: bool | None = None, check=False) -> list[Exception]:
414
- outputdir = outputdir
415
- fast = fast or False
416
- errors: list[Exception] = []
417
-
418
- for example in EXAMPLES:
419
- example_dir = outputdir / example
420
- if not fast or not example_dir.exists():
421
- try:
422
- build_example(example=example, outputdir=outputdir)
423
- except Exception as e:
424
- if check:
425
- raise
426
- errors.append(e)
427
-
428
- try:
429
- generate_css(outputdir=outputdir)
430
- except Exception as e:
431
- if check:
432
- raise
433
- errors.append(e)
434
-
435
- try:
436
- build_index_html(outputdir=outputdir)
437
- except Exception as e:
438
- if check:
439
- raise
440
- errors.append(e)
441
-
442
- return errors
443
-
444
-
445
- def main() -> int:
446
- args = parse_args()
447
- outputdir = args.outputdir
448
- fast = args.fast
449
- build(outputdir=outputdir, fast=fast)
450
- return 0
451
-
452
-
453
- if __name__ == "__main__":
454
- import sys
455
-
456
- sys.argv.append("--fast")
457
- main()
1
+ import argparse
2
+ import subprocess
3
+ from pathlib import Path
4
+ from shutil import copytree, rmtree, which
5
+
6
+ CSS_CONTENT = """
7
+ /* CSS Reset & Variables */
8
+ *, *::before, *::after {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ :root {
15
+ --color-background: #121212;
16
+ --color-surface: #252525;
17
+ --color-surface-transparent: rgba(30, 30, 30, 0.95);
18
+ --color-text: #E0E0E0;
19
+ --spacing-sm: 5px;
20
+ --spacing-md: 10px;
21
+ --spacing-lg: 15px;
22
+ --transition-speed: 0.3s;
23
+ --font-family: 'Roboto Condensed', sans-serif;
24
+ --nav-width: 250px;
25
+ --border-radius: 5px;
26
+ }
27
+
28
+ /* Base Styles */
29
+ body {
30
+ background-color: var(--color-background);
31
+ color: var(--color-text);
32
+ margin: 0;
33
+ padding: 0;
34
+ font-family: var(--font-family);
35
+ min-height: 100vh;
36
+ display: grid;
37
+ grid-template-rows: 1fr;
38
+ }
39
+
40
+ /* Splash Screen */
41
+ .splash-screen {
42
+ position: fixed;
43
+ inset: 0;
44
+ background-color: var(--color-background);
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ z-index: 2000;
49
+ transition: opacity var(--transition-speed) ease-out;
50
+ }
51
+
52
+ .splash-text {
53
+ font-size: 14vw;
54
+ color: var(--color-text);
55
+ font-weight: 300;
56
+ font-family: var(--font-family);
57
+ opacity: 0;
58
+ transition: opacity var(--transition-speed) ease-in;
59
+ }
60
+
61
+ /* Layout */
62
+ .content-wrapper {
63
+ position: relative;
64
+ width: 100%;
65
+ height: 100vh;
66
+ overflow-x: hidden;
67
+ }
68
+
69
+ /* Navigation */
70
+ .nav-trigger {
71
+ position: fixed;
72
+ left: var(--spacing-md);
73
+ top: var(--spacing-md);
74
+ padding: 15px 30px;
75
+ z-index: 1001;
76
+ background-color: var(--color-surface);
77
+ border-radius: var(--border-radius);
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ cursor: pointer;
82
+ color: var(--color-text);
83
+ font-size: 24px;
84
+ transition: background-color var(--transition-speed) ease;
85
+ }
86
+
87
+ .nav-trigger:hover {
88
+ background-color: var(--color-surface-transparent);
89
+ }
90
+
91
+ .nav-trigger .fa-chevron-down {
92
+ margin-left: 10px;
93
+ transition: transform var(--transition-speed) ease;
94
+ }
95
+
96
+ .nav-pane.visible + .nav-trigger .fa-chevron-down {
97
+ transform: rotate(180deg);
98
+ }
99
+
100
+ .nav-pane {
101
+ position: fixed;
102
+ left: var(--spacing-md);
103
+ top: 60px;
104
+ width: var(--nav-width);
105
+ height: auto;
106
+ background-color: var(--color-surface-transparent);
107
+ border-radius: var(--border-radius);
108
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
109
+ transform: translateY(-20px);
110
+ opacity: 0;
111
+ pointer-events: none;
112
+ transition: transform var(--transition-speed) ease,
113
+ opacity var(--transition-speed) ease;
114
+ }
115
+
116
+ .nav-pane.visible {
117
+ transform: translateY(0);
118
+ opacity: 1;
119
+ pointer-events: auto;
120
+ }
121
+
122
+ /* Main Content */
123
+ .main-content {
124
+ width: 100%;
125
+ height: 100%;
126
+ padding: 0;
127
+ overflow: hidden;
128
+ }
129
+
130
+ #example-frame {
131
+ width: 100%;
132
+ height: 100%;
133
+ border: none;
134
+ background-color: var(--color-background);
135
+ overflow: auto;
136
+ }
137
+
138
+ /* Example Links */
139
+ .example-link {
140
+ margin: var(--spacing-sm) var(--spacing-md);
141
+ padding: var(--spacing-lg) var(--spacing-md);
142
+ border-radius: var(--border-radius);
143
+ display: block;
144
+ text-decoration: none;
145
+ color: var(--color-text);
146
+ background-color: var(--color-surface);
147
+ transition: background-color var(--transition-speed) ease-in-out,
148
+ box-shadow var(--transition-speed) ease-in-out;
149
+ position: relative;
150
+ padding-right: 35px;
151
+ }
152
+
153
+ .example-link:hover {
154
+ background-color: var(--color-surface-transparent);
155
+ box-shadow: var(--shadow-hover, 0 0 10px rgba(255, 255, 255, 0.1));
156
+ }
157
+
158
+ .example-link:last-child {
159
+ margin-bottom: var(--spacing-md);
160
+ }
161
+
162
+ /* Accessibility */
163
+ @media (prefers-reduced-motion: reduce) {
164
+ *, *::before, *::after {
165
+ animation-duration: 0.01ms !important;
166
+ animation-iteration-count: 1 !important;
167
+ transition-duration: 0.01ms !important;
168
+ scroll-behavior: auto !important;
169
+ }
170
+ }
171
+ """
172
+
173
+ INDEX_TEMPLATE = """<!DOCTYPE html>
174
+ <html lang="en">
175
+ <head>
176
+ <meta charset="UTF-8">
177
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
178
+ <title>FastLED Examples</title>
179
+ <link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300&display=swap" rel="stylesheet">
180
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
181
+ <link rel="stylesheet" href="index.css">
182
+ </head>
183
+ <body>
184
+ <div class="splash-screen">
185
+ <div class="splash-text">FastLED</div>
186
+ </div>
187
+ <div class="content-wrapper">
188
+ <div class="nav-trigger">Examples <i class="fas fa-chevron-down"></i></div>
189
+ <nav class="nav-pane">
190
+ {example_links}
191
+ </nav>
192
+ <main class="main-content">
193
+ <iframe id="example-frame" title="Example Content"></iframe>
194
+ </main>
195
+ </div>
196
+ <script>
197
+ document.addEventListener('DOMContentLoaded', function() {{
198
+ const splashScreen = document.querySelector('.splash-screen');
199
+ const splashText = document.querySelector('.splash-text');
200
+
201
+ // Ensure splash screen always gets removed
202
+ const removeSplashScreen = () => {{
203
+ splashScreen.style.opacity = '0';
204
+ setTimeout(() => {{
205
+ splashScreen.style.display = 'none';
206
+ }}, 500);
207
+ }};
208
+
209
+ // Set a maximum time the splash screen can stay
210
+ const maxSplashTime = setTimeout(removeSplashScreen, 2000); // Reduced from 5000ms to 2000ms
211
+
212
+ // Try to do nice fade-in/fade-out when possible
213
+ try {{
214
+ // Add a fallback timer in case font loading fails silently
215
+ const fontTimeout = setTimeout(() => {{
216
+ splashText.style.opacity = '1';
217
+ setTimeout(removeSplashScreen, 1000);
218
+ }}, 1000);
219
+
220
+ Promise.all([
221
+ // Wrap font loading in a timeout promise
222
+ Promise.race([
223
+ document.fonts.ready,
224
+ new Promise((_, reject) => setTimeout(reject, 1500))
225
+ ]),
226
+ new Promise(resolve => {{
227
+ if (document.readyState === 'complete') {{
228
+ resolve();
229
+ }} else {{
230
+ window.addEventListener('load', resolve);
231
+ }}
232
+ }})
233
+ ]).then(() => {{
234
+ clearTimeout(maxSplashTime);
235
+ clearTimeout(fontTimeout);
236
+ splashText.style.opacity = '1';
237
+ setTimeout(removeSplashScreen, 1500);
238
+ }}).catch(() => {{
239
+ // If either promise fails, ensure splash screen is removed
240
+ clearTimeout(maxSplashTime);
241
+ removeSplashScreen();
242
+ }});
243
+ }} catch (e) {{
244
+ // Final fallback if anything goes wrong
245
+ console.warn('Splash screen error:', e);
246
+ removeSplashScreen();
247
+ }}
248
+ const links = document.querySelectorAll('.example-link');
249
+ const iframe = document.getElementById('example-frame');
250
+ const navPane = document.querySelector('.nav-pane');
251
+ const navTrigger = document.querySelector('.nav-trigger');
252
+
253
+ // First add checkmarks to all links
254
+ links.forEach(link => {{
255
+ // Add the checkmark span to each link
256
+ const checkmark = document.createElement('i');
257
+ checkmark.className = 'fas fa-check';
258
+ checkmark.style.display = 'none';
259
+ checkmark.style.position = 'absolute';
260
+ checkmark.style.right = '10px';
261
+ checkmark.style.top = '50%';
262
+ checkmark.style.transform = 'translateY(-50%)';
263
+ checkmark.style.color = '#E0E0E0';
264
+ link.appendChild(checkmark);
265
+ }});
266
+
267
+ // Now load first example and show its checkmark
268
+ if (links.length > 0) {{
269
+ // Try to find SdCard example first
270
+ let startLink = Array.from(links).find(link => link.textContent === 'SdCard') || links[0];
271
+ iframe.src = startLink.getAttribute('href');
272
+ startLink.classList.add('active');
273
+ startLink.querySelector('.fa-check').style.display = 'inline-block';
274
+ }}
275
+
276
+ // Add click handlers
277
+ links.forEach(link => {{
278
+ link.addEventListener('click', function(e) {{
279
+ e.preventDefault();
280
+ // Hide all checkmarks
281
+ links.forEach(l => {{
282
+ l.querySelector('.fa-check').style.display = 'none';
283
+ l.classList.remove('active');
284
+ }});
285
+ // Show this checkmark
286
+ this.querySelector('.fa-check').style.display = 'inline-block';
287
+ this.classList.add('active');
288
+ iframe.src = this.getAttribute('href');
289
+ hideNav(); // Hide nav after selection
290
+ }});
291
+ }});
292
+
293
+ function showNav() {{
294
+ navPane.classList.add('visible');
295
+ navPane.style.opacity = '1';
296
+ }}
297
+
298
+ function hideNav() {{
299
+ navPane.style.opacity = '0'; // Start fade out
300
+ setTimeout(() => {{
301
+ navPane.classList.remove('visible');
302
+ }}, 300);
303
+ }}
304
+
305
+ // Click handlers for nav
306
+ navTrigger.addEventListener('click', (e) => {{
307
+ e.stopPropagation();
308
+ if (navPane.classList.contains('visible')) {{
309
+ hideNav();
310
+ }} else {{
311
+ showNav();
312
+ }}
313
+ }});
314
+
315
+ // Close menu when clicking anywhere in the document
316
+ document.addEventListener('click', (e) => {{
317
+ if (navPane.classList.contains('visible') &&
318
+ !navPane.contains(e.target) &&
319
+ !navTrigger.contains(e.target)) {{
320
+ hideNav();
321
+ }}
322
+ }});
323
+
324
+ // Close when clicking iframe
325
+ iframe.addEventListener('load', () => {{
326
+ iframe.contentDocument?.addEventListener('click', () => {{
327
+ if (navPane.classList.contains('visible')) {{
328
+ hideNav();
329
+ }}
330
+ }});
331
+ }});
332
+
333
+ // Initial state
334
+ hideNav();
335
+ }});
336
+ </script>
337
+ </body>
338
+ </html>
339
+ """
340
+
341
+
342
+ EXAMPLES = [
343
+ "wasm",
344
+ "Chromancer",
345
+ "LuminescentGrand",
346
+ "FxSdCard",
347
+ "FxNoiseRing",
348
+ "FxWater",
349
+ ]
350
+
351
+
352
+ def _exec(cmd: str) -> None:
353
+ subprocess.run(cmd, shell=True, check=True)
354
+
355
+
356
+ def build_example(example: str, outputdir: Path) -> None:
357
+ if not which("fastled"):
358
+ raise FileNotFoundError("fastled executable not found")
359
+ src_dir = outputdir / example / "src"
360
+ _exec(f"fastled --init={example} {src_dir}")
361
+ assert src_dir.exists()
362
+ _exec(f"fastled {src_dir / example} --just-compile")
363
+ fastled_dir = src_dir / example / "fastled_js"
364
+ assert fastled_dir.exists(), f"fastled dir {fastled_dir} not found"
365
+ # now copy it to the example dir
366
+ example_dir = outputdir / example
367
+ copytree(fastled_dir, example_dir, dirs_exist_ok=True)
368
+ # now remove the src dir
369
+ rmtree(src_dir, ignore_errors=True)
370
+ print(f"Built {example} example in {example_dir}")
371
+ assert (example_dir / "fastled.wasm").exists()
372
+
373
+
374
+ def generate_css(outputdir: Path) -> None:
375
+ css_file = outputdir / "index.css"
376
+ # with open(css_file, "w") as f:
377
+ # f.write(CSS_CONTENT, encoding="utf-8")
378
+ css_file.write_text(CSS_CONTENT, encoding="utf-8")
379
+
380
+
381
+ def build_index_html(outputdir: Path) -> None:
382
+ outputdir = outputdir
383
+ assert (
384
+ outputdir.exists()
385
+ ), f"Output directory {outputdir} not found, you should run build_example first"
386
+ index_html = outputdir / "index.html"
387
+
388
+ examples = [f for f in outputdir.iterdir() if f.is_dir()]
389
+ examples = sorted(examples)
390
+
391
+ example_links = "\n".join(
392
+ f' <a class="example-link" href="{example.name}/index.html">{example.name}</a>'
393
+ for example in examples
394
+ )
395
+
396
+ with open(index_html, "w") as f:
397
+ f.write(INDEX_TEMPLATE.format(example_links=example_links))
398
+
399
+
400
+ def parse_args() -> argparse.Namespace:
401
+ parser = argparse.ArgumentParser(description="Build FastLED example site")
402
+ parser.add_argument(
403
+ "--outputdir", type=Path, help="Output directory", required=True
404
+ )
405
+ parser.add_argument(
406
+ "--fast",
407
+ action="store_true",
408
+ help="Skip regenerating existing examples, only rebuild index.html and CSS",
409
+ )
410
+ return parser.parse_args()
411
+
412
+
413
+ def build(outputdir: Path, fast: bool | None = None, check=False) -> list[Exception]:
414
+ outputdir = outputdir
415
+ fast = fast or False
416
+ errors: list[Exception] = []
417
+
418
+ for example in EXAMPLES:
419
+ example_dir = outputdir / example
420
+ if not fast or not example_dir.exists():
421
+ try:
422
+ build_example(example=example, outputdir=outputdir)
423
+ except Exception as e:
424
+ if check:
425
+ raise
426
+ errors.append(e)
427
+
428
+ try:
429
+ generate_css(outputdir=outputdir)
430
+ except Exception as e:
431
+ if check:
432
+ raise
433
+ errors.append(e)
434
+
435
+ try:
436
+ build_index_html(outputdir=outputdir)
437
+ except Exception as e:
438
+ if check:
439
+ raise
440
+ errors.append(e)
441
+
442
+ return errors
443
+
444
+
445
+ def main() -> int:
446
+ args = parse_args()
447
+ outputdir = args.outputdir
448
+ fast = args.fast
449
+ build(outputdir=outputdir, fast=fast)
450
+ return 0
451
+
452
+
453
+ if __name__ == "__main__":
454
+ import sys
455
+
456
+ sys.argv.append("--fast")
457
+ main()