Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 112 additions & 12 deletions 3d-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class JulesSubwayScene {
this.trainTrail = null;

this.colors = this.data.colors;
this.fallbackActive = false;

this.onWindowResize = this.onWindowResize.bind(this);
this.onPointerMove = this.onPointerMove.bind(this);
Expand All @@ -60,7 +61,10 @@ class JulesSubwayScene {
}

try {
this.setupRenderer();
const rendererReady = this.setupRenderer();
if (!rendererReady) {
return;
}
this.setupScene();
this.setupCamera();
this.setupLights();
Expand Down Expand Up @@ -90,14 +94,25 @@ class JulesSubwayScene {
this.canvas = document.getElementById('three-canvas');
this.stageElement = this.canvas ? this.canvas.parentElement : null;
}
if (!this.stageElement) {
this.stageElement = document.querySelector('.hero-visual-stage');
}

if (!this.canvas) {
console.warn('3D canvas element not found; skipping NYC subway scene.');
this.activateFallback('Interactive canvas missing');
return false;
}

if (typeof THREE === 'undefined') {
console.warn('Three.js is not available; cannot initialise NYC subway scene.');
this.activateFallback('3D engine library unavailable');
return false;
}

if (!this.hasWebGLSupport()) {
console.warn('WebGL not available; showing static subway map fallback.');
this.activateFallback('WebGL unavailable');
return false;
}

Expand Down Expand Up @@ -139,17 +154,87 @@ class JulesSubwayScene {
}

setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
alpha: true
});
try {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
alpha: true
});
} catch (error) {
console.error('Failed to create WebGL renderer', error);
this.activateFallback('WebGL renderer initialisation failed');
return false;
}
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
const { width, height } = this.getCanvasSize();
this.renderer.setSize(width, height, false);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.outputEncoding = THREE.sRGBEncoding;

return true;
}

hasWebGLSupport() {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return false;
}

if (!('WebGLRenderingContext' in window)) {
return false;
}

try {
const probeCanvas = document.createElement('canvas');
const contexts = ['webgl2', 'webgl', 'experimental-webgl'];

for (const contextName of contexts) {
const context = probeCanvas.getContext(contextName, { failIfMajorPerformanceCaveat: true });
if (context) {
if (typeof context.getExtension === 'function') {
const loseContext = context.getExtension('WEBGL_lose_context');
if (loseContext && typeof loseContext.loseContext === 'function') {
loseContext.loseContext();
}
}
return true;
}
}
} catch (error) {
console.warn('WebGL support probe failed', error);
}

return false;
}

activateFallback(reason) {
if (!this.stageElement || this.fallbackActive) {
return;
}

this.fallbackActive = true;
this.stageElement.classList.add('hero-visual-stage--fallback');

if (this.canvas) {
this.canvas.setAttribute('aria-hidden', 'true');
this.canvas.setAttribute('data-webgl-disabled', 'true');
}

const badge = this.stageElement.querySelector('.hero-3d-badge');
if (badge) {
badge.textContent = 'Static View';
}

const subtitle = this.stageElement.querySelector('.hero-3d-subtitle');
if (subtitle) {
const baseMessage = 'Static NYC subway map preview';
subtitle.textContent = reason ? `${baseMessage} · ${reason}` : baseMessage;
}

const title = this.stageElement.querySelector('.hero-3d-title');
if (title) {
title.textContent = 'NYC Subway Map Overview';
}
}

setupScene() {
Expand Down Expand Up @@ -660,22 +745,37 @@ let particleField = null;

document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('three-canvas');
if (canvas && typeof THREE !== 'undefined') {
const stageElement = canvas ? canvas.parentElement : document.querySelector('.hero-visual-stage');
let fallbackActive = stageElement ? stageElement.classList.contains('hero-visual-stage--fallback') : false;

if (canvas) {
subwayScene = new JulesSubwayScene(canvas);
subwayScene.init();
} else if (!canvas) {
console.warn('NYC subway canvas not found on this page.');
fallbackActive = subwayScene ? subwayScene.fallbackActive : fallbackActive;
} else {
console.warn('NYC subway canvas not found on this page.');
if (stageElement) {
stageElement.classList.add('hero-visual-stage--fallback');
fallbackActive = true;
}
}

if (typeof THREE === 'undefined' && stageElement && !fallbackActive) {
console.warn('Three.js failed to load; NYC subway scene disabled.');
stageElement.classList.add('hero-visual-stage--fallback');
fallbackActive = true;
}

const particleContainer = document.getElementById('particle-container');
particleField = new HeroParticleField(particleContainer);
particleField.init();
if (particleContainer && !fallbackActive) {
particleField = new HeroParticleField(particleContainer);
particleField.init();
}

window.JulesAI3D = {
scene: subwayScene,
particles: particleField
particles: particleField,
fallbackActive
};
});

Expand Down
11 changes: 10 additions & 1 deletion build_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,16 @@ def create_static_site():
print(f"⚠️ {template_file} not found, skipping...")

# Copy static assets
static_files = ['style.css', 'script.js', 'image.png', 'placeholder.jpg', 'nyc_subway_map.png']
static_files = [
'style.css',
'script.js',
'image.png',
'placeholder.jpg',
'nyc_subway_map.png',
'nyc_subway_map_optimized.jpg',
'3d-engine.js',
'3d-engine-scene-data.js'
]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
for static_file in static_files:
if os.path.exists(static_file):
print(f"📁 Copying {static_file}...")
Expand Down
37 changes: 37 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,43 @@ a.reddit-join-button:hover {
pointer-events: none;
}

.hero-visual-stage--fallback {
transform: none;
background: linear-gradient(160deg, rgba(15, 23, 42, 0.85) 0%, rgba(15, 23, 42, 0.35) 35%, rgba(15, 23, 42, 0.65) 100%),
url('nyc_subway_map_optimized.jpg') center/cover no-repeat;
box-shadow: 0 28px 90px rgba(15, 23, 42, 0.45);
}

.hero-visual-stage--fallback:hover {
transform: none;
box-shadow: 0 28px 90px rgba(15, 23, 42, 0.45);
}

.hero-visual-stage--fallback::before,
.hero-visual-stage--fallback::after {
opacity: 0;
}

.hero-visual-stage--fallback .three-canvas,
.hero-visual-stage--fallback .particle-container {
display: none !important;
}

.hero-visual-stage--fallback .hero-3d-hud {
background: rgba(6, 14, 32, 0.85);
border-color: rgba(110, 170, 255, 0.25);
}

.hero-visual-stage--fallback .hero-3d-badge {
background: linear-gradient(90deg, #94a3b8, #e2e8f0);
color: #0f172a;
}

.hero-visual-stage--fallback .hero-3d-title,
.hero-visual-stage--fallback .hero-3d-subtitle {
color: #f8fafc;
}

.hero-3d-hud {
position: absolute;
top: var(--space-5);
Expand Down