+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.py b/build.py
new file mode 100755
index 0000000..cfe3c08
--- /dev/null
+++ b/build.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+"""
+Build a single self-contained HTML file from the source files.
+
+Usage:
+ python3 build.py [output.html]
+
+If output is not given, defaults to 'background-final.html'.
+
+This reads config.js to pick the active theme and inlines all CSS,
+JS, and the noise.png texture into a single HTML document. After
+building, the resulting double-standalone HTML can be opened directly in
+a browser (file://) because all external resources are inlined.
+"""
+
+import base64
+import os
+import re
+import sys
+
+ROOT = os.path.dirname(os.path.abspath(__file__))
+
+
+def read(path):
+ with open(os.path.join(ROOT, path), 'r', encoding='utf-8') as f:
+ return f.read()
+
+
+def parse_config():
+ """Extract window.CONFIG values from config.js."""
+ src = read('config.js')
+ out = {}
+ for key in ['debug', 'theme', 'units', 'language', 'timeFormat']:
+ m = re.search(r"\b%s\s*:\s*'([^']*)'" % key, src) or re.search(r'\b%s\s*:\s*(\d+)' % key, src)
+ if m:
+ out[key] = m.group(1)
+ m = re.search(r'lat\s*:\s*([\d.]+)', src)
+ if m:
+ out['lat'] = m.group(1)
+ m = re.search(r'lon\s*:\s*([-\d.]+)', src)
+ if m:
+ out['lon'] = m.group(1)
+ return out
+
+
+def main():
+ out = sys.argv[1] if len(sys.argv) > 1 else 'background-final.html'
+
+ cfg = parse_config()
+ theme = cfg.get('theme', 'ash')
+
+ html = read('background.html')
+
+ # Resolve active theme css
+ theme_css = read(theme + '.css')
+ # Inline the active theme stylesheet at the end of
+ html = html.replace('',
+ '\n',
+ 1)
+
+ # Inline vendor scripts in the body, before the main script block
+ for _, path in [
+ ('three', 'vendor/three.min.js'),
+ ('suncalc', 'vendor/suncalc.js'),
+ ('vanta2', 'vendor/vanta.clouds2.min.js'),
+ ('i18n', 'i18n.js'),
+ ('config', 'config.js'),
+ ]:
+ body = read(path)
+ html = html.replace('',
+ '',
+ 1)
+
+ # Inline noise.png as data URI in the Vanta init script.
+ with open(os.path.join(ROOT, 'noise.png'), 'rb') as f:
+ noise_b64 = base64.b64encode(f.read()).decode('ascii')
+ noise_uri = 'data:image/png;base64,' + noise_b64
+ html = re.sub(
+ r"texturePath:\s*'[^']*'",
+ "texturePath: '" + noise_uri + "'",
+ html)
+
+ with open(os.path.join(ROOT, out), 'w', encoding='utf-8') as f:
+ f.write(html)
+
+ print('Wrote ' + out + ' (' + str(len(html)) + ' bytes)')
+ print('Config used:')
+ for k in ('debug', 'theme', 'units', 'language', 'timeFormat', 'lat', 'lon'):
+ if k in cfg:
+ print(' ' + k + ' = ' + cfg[k])
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file