diff --git a/index.html b/index.html
index 17a4012..c3c2dd7 100644
--- a/index.html
+++ b/index.html
@@ -1,185 +1,187 @@
-
-
+
+
-
-
+
+
+
-
Unpacking...
-
+
- // Error sink persists across replaceWith since it's on window, not the DOM.
- window.addEventListener('error', function(e) {
- var p = document.body || document.documentElement;
- var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div'));
- d.id = '__bundler_err';
- d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto';
- d.textContent = (d.textContent ? d.textContent + String.fromCharCode(10) : '') +
- '[bundle] ' + (e.message || e.type) +
- (e.filename ? ' (' + e.filename.slice(0, 60) + ':' + e.lineno + ')' : '');
- }, true);
+
+
+
+
+
- try {
- const manifestEl = document.querySelector('script[type="__bundler/manifest"]');
- const templateEl = document.querySelector('script[type="__bundler/template"]');
- if (!manifestEl || !templateEl) {
- setStatus('Error: missing bundle data');
- console.error('[bundler] Missing script tags — manifestEl:', !!manifestEl, 'templateEl:', !!templateEl);
- return;
- }
-
- const manifest = JSON.parse(manifestEl.textContent);
- let template = JSON.parse(templateEl.textContent);
-
- const uuids = Object.keys(manifest);
- setStatus('Unpacking ' + uuids.length + ' assets...');
-
- const blobUrls = {};
- await Promise.all(uuids.map(async (uuid) => {
- const entry = manifest[uuid];
- try {
- const binaryStr = atob(entry.data);
- const bytes = new Uint8Array(binaryStr.length);
- for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
-
- let finalBytes = bytes;
- if (entry.compressed) {
- if (typeof DecompressionStream !== 'undefined') {
- const ds = new DecompressionStream('gzip');
- const writer = ds.writable.getWriter();
- const reader = ds.readable.getReader();
- writer.write(bytes);
- writer.close();
- const chunks = [];
- let totalLen = 0;
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- chunks.push(value);
- totalLen += value.length;
- }
- finalBytes = new Uint8Array(totalLen);
- let offset = 0;
- for (const chunk of chunks) { finalBytes.set(chunk, offset); offset += chunk.length; }
- } else {
- console.warn('DecompressionStream not available, asset ' + uuid + ' may not render');
- }
- }
-
- blobUrls[uuid] = URL.createObjectURL(new Blob([finalBytes], { type: entry.mime }));
- } catch (err) {
- console.error('Failed to decode asset ' + uuid + ':', err);
- blobUrls[uuid] = URL.createObjectURL(new Blob([], { type: entry.mime }));
- }
- }));
-
- const extResEl = document.querySelector('script[type="__bundler/ext_resources"]');
- const extResources = extResEl ? JSON.parse(extResEl.textContent) : [];
- const resourceMap = {};
- for (const entry of extResources) {
- if (blobUrls[entry.uuid]) resourceMap[entry.id] = blobUrls[entry.uuid];
- }
-
- setStatus('Rendering...');
- for (const uuid of uuids) template = template.split(uuid).join(blobUrls[uuid]);
-
- // Strip integrity + crossorigin — blob URLs from a file:// document inherit
- // a null origin, so crossorigin forces a CORS fetch that SRI then rejects.
- // The manifest bytes are ours; SRI protects against CDN compromise, not this.
- template = template.replace(/\s+integrity="[^"]*"/gi, '').replace(/\s+crossorigin="[^"]*"/gi, '');
-
- const resourceScript = '
-
-
-
-
-
-
+
-
\ No newline at end of file
+
diff --git a/nginx.conf b/nginx.conf
index 1152a99..f6cb92c 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -1,6 +1,5 @@
server {
- listen 43036 default_server;
- listen [::]:43036 default_server;
+ listen 80;
server_name _;
root /usr/share/nginx/html;
@@ -9,30 +8,50 @@ server {
charset utf-8;
gzip on;
- gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml;
+ gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;
gzip_min_length 1024;
+ gzip_comp_level 6;
+ gzip_vary on;
- # Long cache for static assets — they're content-addressed by path,
- # bust via filename when they change.
- location ~* \.(png|jpg|jpeg|gif|webp|svg|ico|woff2?|ttf)$ {
- expires 30d;
+ # Security headers
+ add_header X-Frame-Options SAMEORIGIN always;
+ add_header X-Content-Type-Options nosniff always;
+ add_header Referrer-Policy strict-origin-when-cross-origin always;
+ add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
+
+ # Vendored libs — versioned, cache hard
+ location /vendor/ {
+ expires 365d;
add_header Cache-Control "public, immutable";
}
- # Short cache for the HTML/JS that drive the SPA so edits show up fast.
- location ~* \.(html|css|js|jsx)$ {
- expires 5m;
+ # Images/fonts — long cache
+ location ~* \.(png|jpg|jpeg|gif|webp|avif|svg|ico|woff2?|ttf)$ {
+ expires 30d;
+ add_header Cache-Control "public, no-transform";
+ }
+
+ # App JS/CSS — short cache so edits show fast
+ location ~* \.(css|js)$ {
+ expires 10m;
add_header Cache-Control "public, must-revalidate";
}
- # Single-page app — unknown paths fall back to index.html, which
- # handles routing via location.hash (#/projects/
).
+ # Never index build sources or patch scripts
+ location ~* \.(jsx|py)$ { return 404; }
+ location /scripts/ { return 404; }
+
+ location = /robots.txt { default_type text/plain; }
+ location = /llms.txt { default_type text/plain; }
+ location = /sitemap.xml { default_type application/xml; }
+
+ # HTML — no stale cache
+ location = /index.html {
+ add_header Cache-Control "no-cache, must-revalidate";
+ }
+
+ # SPA fallback (routing via location.hash)
location / {
try_files $uri $uri/ /index.html;
}
-
- # llms.txt — let crawlers pull it without rewrites.
- location = /llms.txt {
- default_type text/plain;
- }
}
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 0000000..bee52ce
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,6 @@
+User-agent: *
+Allow: /
+Disallow: /vendor/
+Disallow: /scripts/
+
+Sitemap: https://wilddragon.net/sitemap.xml
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 0000000..0e9c036
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1,34 @@
+
+
+
+ https://wilddragon.net/
+ 2026-07-03
+ monthly
+ 1.0
+
+ https://wilddragon.net/images/wild-dragon-logo.jpg
+ Wild Dragon — Broadcast Systems Integration
+
+
+ https://wilddragon.net/images/photos/production-switcher.jpg
+ Production switcher in a broadcast control room
+
+
+ https://wilddragon.net/images/photos/commanders-stadium.jpg
+ Washington Commanders stadium broadcast production
+
+
+ https://wilddragon.net/images/photos/betmgm-production.jpg
+ BetMGM cloud production facility
+
+
+ https://wilddragon.net/images/photos/cvs-aetna.jpg
+ CVS/Aetna broadcast facility
+
+
+ https://wilddragon.net/images/photos/xr-monitor.jpg
+ XR virtual production stage monitor
+
+
+