DedeProGames / GRM-Corridor

huggingface.co
Total runs: 0
24-hour runs: 0
7-day runs: 0
30-day runs: 0
Model's Last Updated: July 24 2026

Introduction of GRM-Corridor

Model Details of GRM-Corridor

The user wants a single self-contained HTML file using Three.js that renders a 3D first-person perspective interactive/animated endless Gothic hallway. Let me break down the requirements:

  1. Dark stone vaulted arch ceiling
  2. Stone pillars
  3. Glossy reflective black-and-white checkered marble floor
  4. Arched stained glass side windows with colorful mosaic patterns (diamond grids, rose wheels, Voronoi cells)
  5. Bright volumetric light shafts/god rays casting vibrant colored light projections onto the floor
  6. Subtle dark vignetting
  7. Smooth camera movement forward down the corridor
  8. Endless (procedurally generated as camera moves)

This is a complex scene. Let me plan the implementation:

  • Use Three.js from CDN
  • Procedurally generate the hallway segments as the camera moves forward
  • The floor will be a checkered pattern using a shader or texture
  • The ceiling will be vaulted arches using geometry
  • Pillars along the sides
  • Stained glass windows with procedural textures (diamond grids, rose wheels, Voronoi cells)
  • Volumetric light shafts using transparent cone/cylinder geometries with additive blending
  • Vignetting via post-processing or a shader overlay
  • Camera moves forward smoothly

Let me think about the endless aspect: I'll create hallway segments and recycle them as the camera passes them.

For the stained glass, I'll create canvas textures with the different patterns.

For volumetric god rays, I'll use transparent geometries with colored materials and additive blending, plus colored point lights.

Let me code this up comprehensively.

I'll structure it as:

  • Scene setup with fog for atmosphere
  • Hallway segment class that creates a chunk of hallway
  • Segment recycling system
  • Stained glass texture generation via canvas
  • Floor with checkered pattern (using a shader material for reflectivity)
  • Vaulted ceiling geometry
  • Pillars with Gothic styling
  • Light shafts as volumetric cones
  • Post-processing vignette via a full-screen quad with a vignette shader
  • Smooth forward movement with slight sway

Let me write this out carefully. I'll use Three.js r128+ from CDN.

For the vaulted ceiling, I can create it using a parametric surface or by constructing it from segments. A simple approach: create a series of arches that form a vault.

Actually, let me think about the geometry more carefully:

  • Floor: checkered plane
  • Walls: stone-textured planes on sides
  • Ceiling: vaulted arch - I can create this by extruding an arch shape along the hallway, or by creating individual arch segments
  • Pillars: cylinders with some Gothic detail (maybe with capitals)
  • Windows: arched openings in the walls with stained glass planes

For the endless hallway, I'll create segments of fixed length and move them behind the camera as it passes.

Let me code this step by step.

For the stained glass patterns:

  1. Diamond grid - simple diamond shapes with colors
  2. Rose wheel - circular pattern like a rose window
  3. Voronoi cells - compute Voronoi diagram on canvas

For volumetric light shafts: I'll create cone geometries pointing down from the windows with additive blending and colored materials. I'll also add colored spotlights/point lights.

For the glossy reflective floor, I can use MeshPhysicalMaterial with high metalness/roughness settings, or use a custom shader. Actually, for true reflections I'd need an environment map or render target. A simpler approach: use a reflective material and add an environment map generated from the scene, or just make it look glossy with high shininess. Let me use MeshPhysicalMaterial with clearcoat for the marble look, and maybe add a simple reflection using a render target if needed. Actually, for a single file, let me use a custom shader that does a simple reflection approximation, or just use MeshStandardMaterial with high metalness.

Actually, let me use a custom floor shader that gives a glossy checkered look with fake reflections. Or I can use PMREMGenerator for environment mapping. Let me keep it simpler: use MeshPhysicalMaterial with metalness 0.9, roughness 0.1, and the checkered texture as the base color.

Let me write the code now. I'll make it comprehensive and visually impressive.

For the vignette, I'll add a post-processing pass using a custom shader material on a fullscreen quad rendered on top.

Let me structure the code:

<!DOCTYPE html>
<html>
<head>
  <style>body { margin: 0; overflow: hidden; background: #000; }</style>
</head>
<body>
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js"
    }
  }
  </script>
  <script type="module">
    // ... all the code
  </script>
</body>
</html>

Let me write the full implementation now. I need to be careful about:

  • Segment management for the endless hallway
  • Stained glass texture generation
  • Volumetric light rendering
  • Performance (don't create too many geometries)

I'll create a HallwaySegment class that builds one segment of the hallway, and maintain a pool of segments positioned ahead of the camera.

For the vaulted ceiling, I'll create a series of intersecting arches. A Gothic rib vault can be approximated by creating arch frames.

Let me think about the segment length - maybe 10 units per segment, with 6-8 segments visible ahead.

For the stained glass windows, each segment will have 2 windows (left and right). I'll cycle through the different patterns.

Let me code this up properly now.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Endless Gothic Hallway</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #000; }
canvas { display: block; }
#vignette {
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 10;
  background: radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.7) 100%);
}
#info {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  color: rgba(200,180,150,0.6); font-family: Georgia, serif; font-size: 14px;
  pointer-events: none; z-index: 11; text-align: center;
  text-shadow: 0 0 10px rgba(0,0,0,0.8);
}
</style>
</head>
<body>
<div id="vignette"></div>
<div id="info">Move mouse to look around · The endless corridor goes on forever</div>
<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js"
  }
}
</script>
<script type="module">
import * as THREE from 'three';

// ── Globals ──
const SEGMENT_LENGTH = 12;
const HALLWAY_WIDTH = 8;
const HALLWAY_HEIGHT = 9;
const NUM_SEGMENTS = 10;
const CAMERA_SPEED = 3.5;
const SEGMENT_POOL = [];
const LIGHT_SHAFTS = [];

let camera, scene, renderer, clock;
let mouseX = 0, mouseY = 0, targetLookX = 0, targetLookY = 0;
let cameraYOffset = 1.7;
let time = 0;

// ── Texture Generators ──
function createStoneTexture() {
  const c = document.createElement('canvas');
  c.width = 512; c.height = 512;
  const ctx = c.getContext('2d');
  ctx.fillStyle = '#2a2825';
  ctx.fillRect(0, 0, 512, 512);
  for (let i = 0; i < 6000; i++) {
    const x = Math.random() * 512, y = Math.random() * 512;
    const b = 30 + Math.random() * 25;
    ctx.fillStyle = `rgb(${b},${b-2},${b-4})`;
    ctx.fillRect(x, y, 1 + Math.random() * 3, 1 + Math.random() * 3);
  }
  for (let i = 0; i < 800; i++) {
    const x = Math.random() * 512, y = Math.random() * 512;
    ctx.fillStyle = `rgba(0,0,0,${Math.random() * 0.3})`;
    ctx.beginPath();
    ctx.arc(x, y, Math.random() * 4, 0, Math.PI * 2);
    ctx.fill();
  }
  const tex = new THREE.CanvasTexture(c);
  tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
  return tex;
}

function createCheckeredTexture() {
  const c = document.createElement('canvas');
  c.width = 512; c.height = 512;
  const ctx = c.getContext('2d');
  const tileSize = 64;
  for (let y = 0; y < 8; y++) {
    for (let x = 0; x < 8; x++) {
      const dark = (x + y) % 2 === 0;
      const r = dark ? 15 + Math.random() * 5 : 210 + Math.random() * 20;
      const g = dark ? 14 + Math.random() * 5 : 208 + Math.random() * 20;
      const b = dark ? 16 + Math.random() * 5 : 215 + Math.random() * 20;
      ctx.fillStyle = `rgb(${r},${g},${b})`;
      ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
      // marble veining
      if (dark) {
        ctx.strokeStyle = `rgba(40,38,35,${0.3 + Math.random() * 0.3})`;
        ctx.lineWidth = 0.5;
        ctx.beginPath();
        ctx.moveTo(x * tileSize, y * tileSize + Math.random() * tileSize);
        ctx.bezierCurveTo(
          x * tileSize + tileSize * 0.3, y * tileSize + Math.random() * tileSize,
          x * tileSize + tileSize * 0.7, y * tileSize + Math.random() * tileSize,
          x * tileSize + tileSize, y * tileSize + Math.random() * tileSize
        );
        ctx.stroke();
      }
    }
  }
  const tex = new THREE.CanvasTexture(c);
  tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
  tex.repeat.set(1, 1);
  return tex;
}

function createStainedGlassTexture(patternType, seed) {
  const c = document.createElement('canvas');
  c.width = 512; c.height = 512;
  const ctx = c.getContext('2d');
  const rng = mulberry32(seed);

  const palette = [
    '#c41e3a', '#1e3ac4', '#c4a01e', '#1ec43a', '#8b1ec4',
    '#c41e8b', '#1ec4c4', '#c46e1e', '#4a0e4e', '#0e4e4a',
    '#e8d44d', '#d44d8b', '#4dd4e8', '#e84d4d', '#4de88b',
    '#7b2d8b', '#2d7b8b', '#8b7b2d', '#2d8b4d', '#8b2d4d'
  ];

  // Arch mask: Gothic pointed arch
  function isInsideArch(x, y) {
    const cx = 256, cy = 380, w = 200, h = 300;
    if (x < cx - w / 2 || x > cx + w / 2) return false;
    if (y > cy) return false;
    if (y > cy - h) return false;
    const dx = Math.abs(x - cx);
    const archY = cy - h + h * 0.6;
    if (y > archY) {
      // lower rectangular part
      return true;
    }
    // pointed arch: two intersecting circles
    const r = w * 1.1;
    const leftCenter = { x: cx - w * 0.35, y: archY + r - h * 0.55 };
    const rightCenter = { x: cx + w * 0.35, y: archY + r - h * 0.55 };
    const d1 = Math.sqrt((x - leftCenter.x) ** 2 + (y - leftCenter.y) ** 2);
    const d2 = Math.sqrt((x - rightCenter.x) ** 2 + (y - rightCenter.y) ** 2);
    return d1 < r && d2 < r;
  }

  ctx.fillStyle = '#0a0810';
  ctx.fillRect(0, 0, 512, 512);

  const color = () => palette[Math.floor(rng() * palette.length)];
  const leadColor = '#1a1a2e';

  if (patternType === 'diamond') {
    // Diamond grid pattern
    const size = 50;
    ctx.save();
    ctx.beginPath();
    for (let x = 0; x <= 512; x += 4) {
      for (let y = 0; y <= 512; y += 4) {
        if (isInsideArch(x, y)) ctx.rect(x, y, 4, 4);
      }
    }
    ctx.clip();
    for (let y = -size; y < 512 + size; y += size) {
      for (let x = -size; x < 512 + size; x += size) {
        const cx = x + size / 2, cy = y + size / 2;
        const s = size / 2 - 2;
        ctx.fillStyle = color();
        ctx.beginPath();
        ctx.moveTo(cx, cy - s);
        ctx.lineTo(cx + s, cy);
        ctx.lineTo(cx, cy + s);
        ctx.lineTo(cx - s, cy);
        ctx.closePath();
        ctx.fill();
        // inner diamond
        ctx.fillStyle = color();
        ctx.beginPath();
        ctx.moveTo(cx, cy - s * 0.5);
        ctx.lineTo(cx + s * 0.5, cy);
        ctx.lineTo(cx, cy + s * 0.5);
        ctx.lineTo(cx - s * 0.5, cy);
        ctx.closePath();
        ctx.fill();
      }
    }
    // lead lines
    ctx.strokeStyle = leadColor;
    ctx.lineWidth = 3;
    for (let y = -size; y < 512 + size; y += size) {
      for (let x = -size; x < 512 + size; x += size) {
        const cx = x + size / 2, cy = y + size / 2;
        const s = size / 2 - 2;
        ctx.beginPath();
        ctx.moveTo(cx, cy - s);
        ctx.lineTo(cx + s, cy);
        ctx.lineTo(cx, cy + s);
        ctx.lineTo(cx - s, cy);
        ctx.closePath();
        ctx.stroke();
      }
    }
    ctx.restore();
  } else if (patternType === 'rose') {
    // Rose window pattern
    const cx = 256, cy = 280, maxR = 220;
    ctx.save();
    ctx.beginPath();
    for (let x = 0; x <= 512; x += 4) {
      for (let y = 0; y <= 512; y += 4) {
        if (isInsideArch(x, y)) ctx.rect(x, y, 4, 4);
      }
    }
    ctx.clip();
    const rings = 4;
    const petals = 8;
    for (let ring = rings; ring >= 0; ring--) {
      const r = maxR * (ring / rings);
      const nextR = maxR * ((ring + 1) / rings);
      for (let p = 0; p < petals; p++) {
        const a1 = (p / petals) * Math.PI * 2;
        const a2 = ((p + 1) / petals) * Math.PI * 2;
        const midA = (a1 + a2) / 2;
        ctx.fillStyle = color();
        ctx.beginPath();
        ctx.moveTo(cx + Math.cos(a1) * r, cy + Math.sin(a1) * r);
        ctx.lineTo(cx + Math.cos(midA) * nextR, cy + Math.sin(midA) * nextR);
        ctx.lineTo(cx + Math.cos(a2) * r, cy + Math.sin(a2) * r);
        ctx.closePath();
        ctx.fill();
        ctx.strokeStyle = leadColor;
        ctx.lineWidth = 2.5;
        ctx.stroke();
      }
      // center circle of ring
      for (let p = 0; p < petals; p++) {
        const a1 = (p / petals) * Math.PI * 2;
        const a2 = ((p + 0.5) / petals) * Math.PI * 2;
        ctx.fillStyle = color();
        ctx.beginPath();
        ctx.arc(cx + Math.cos((a1 + a2) / 2) * (r + nextR) * 0.5,
                cy + Math.sin((a1 + a2) / 2) * (r + nextR) * 0.5,
                (nextR - r) * 0.35, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = leadColor;
        ctx.lineWidth = 2;
        ctx.stroke();
      }
    }
    // center boss
    ctx.fillStyle = color();
    ctx.beginPath();
    ctx.arc(cx, cy, maxR * 0.12, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = leadColor;
    ctx.lineWidth = 3;
    ctx.stroke();
    ctx.restore();
  } else if (patternType === 'voronoi') {
    // Voronoi cells
    const points = [];
    for (let i = 0; i < 30; i++) {
      points.push({
        x: 80 + rng() * 352,
        y: 40 + rng() * 432,
        color: color()
      });
    }
    // Simple pixel-based Voronoi
    const imgData = ctx.createImageData(512, 512);
    const step = 2;
    for (let y = 0; y < 512; y += step) {
      for (let x = 0; x < 512; x += step) {
        if (!isInsideArch(x, y)) continue;
        let minD = Infinity, closest = 0, secondD = Infinity;
        for (let i = 0; i < points.length; i++) {
          const d = (x - points[i].x) ** 2 + (y - points[i].y) ** 2;
          if (d < minD) {
            secondD = minD;
            minD = d;
            closest = i;
          } else if (d < secondD) {
            secondD = d;
          }
        }
        const edgeDist = secondD - minD;
        const isEdge = edgeDist < 900;
        const hex = isEdge ? '#1a1a2e' : points[closest].color;
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        for (let dy = 0; dy < step && y + dy < 512; dy++) {
          for (let dx = 0; dx < step && x + dx < 512; dx++) {
            const idx = ((y + dy) * 512 + (x + dx)) * 4;
            imgData.data[idx] = r;
            imgData.data[idx + 1] = g;
            imgData.data[idx + 2] = b;
            imgData.data[idx + 3] = 255;
          }
        }
      }
    }
    ctx.putImageData(imgData, 0, 0);
    // Draw lead lines between cells
    ctx.strokeStyle = leadColor;
    ctx.lineWidth = 2.5;
    for (let i = 0; i < points.length; i++) {
      for (let j = i + 1; j < points.length; j++) {
        const d = Math.sqrt((points[i].x - points[j].x) ** 2 + (points[i].y - points[j].y) ** 2);
        if (d < 200) {
          // perpendicular bisector clipped to arch
          const mx = (points[i].x + points[j].x) / 2;
          const my = (points[i].y + points[j].y) / 2;
          const dx = points[j].x - points[i].x;
          const dy = points[j].y - points[i].y;
          const len = 150;
          ctx.beginPath();
          ctx.moveTo(mx - (dy / d) * len, my - (dx / d) * len);
          ctx.lineTo(mx + (dy / d) * len, my + (dx / d) * len);
          ctx.stroke();
        }
      }
    }
  }

  // Dark stone frame around arch
  ctx.save();
  ctx.strokeStyle = '#1e1c18';
  ctx.lineWidth = 14;
  ctx.beginPath();
  for (let x = 0; x <= 512; x += 2) {
    for (let y = 0; y <= 512; y += 2) {
      const inside = isInsideArch(x, y);
      const outside = !isInsideArch(x + (inside ? 1 : -1), y);
      if (inside && outside) {
        ctx.moveTo(x, y);
      }
    }
  }
  // Draw arch outline properly
  const archCx = 256, archW = 200, archBase = 380, archTop = 80;
  const r = archW * 1.1;
  ctx.beginPath();
  ctx.moveTo(archCx - archW / 2, archBase);
  ctx.lineTo(archCx - archW / 2, archBase - archW * 0.7);
  ctx.arc(archCx - archW * 0.35, archBase - archW * 0.7 + r - (archBase - archTop - archW * 0.7), r, Math.PI * 0.8, Math.PI * 1.15, true);
  ctx.arc(archCx + archW * 0.35, archBase - archW * 0.7 + r - (archBase - archTop - archW * 0.7), r, Math.PI * 1.15, Math.PI * 0.2, true);
  ctx.lineTo(archCx + archW / 2, archBase);
  ctx.closePath();
  ctx.stroke();
  ctx.restore();

  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  return tex;
}

function mulberry32(a) {
  return function () {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

// ── Geometry Builders ──
function createVaultedCeilingGeometry() {
  const group = new THREE.Group();
  const stoneTex = createStoneTexture();
  const stoneMat = new THREE.MeshStandardMaterial({
    map: stoneTex, roughness: 0.95, metalness: 0.05, color: 0x3a3530
  });

  // Create rib vault: two intersecting barrel vaults
  // Main longitudinal vault
  const segments = 20;
  const shape = new THREE.Shape();
  shape.moveTo(-HALLWAY_WIDTH / 2, 0);
  shape.quadraticCurveTo(0, HALLWAY_HEIGHT * 1.1, HALLWAY_WIDTH / 2, 0);

  const curve = new THREE.LineCurve3(
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(0, 0, SEGMENT_LENGTH)
  );
  const extrudeSettings = {
    steps: 1, bevelEnabled: false, extrudePath: curve
  };
  // Actually let's build the vault manually with custom geometry for better control

  // Cross vault: build from triangles
  const geo = new THREE.BufferGeometry();
  const vertices = [];
  const normals = [];
  const uvs = [];
  const vaultSegments = 16;
  const archSegments = 12;
  const halfW = HALLWAY_WIDTH / 2;
  const archHeight = HALLWAY_HEIGHT * 1.05;

  // Generate points on the vault surface (pointed arch profile extruded along Z)
  function getArchPoint(t, z) {
    // Pointed arch: two circular arcs
    const x = (t - 0.5) * HALLWAY_WIDTH;
    const r = halfW * 1.3;
    let y;
    if (Math.abs(t - 0.5) < 0.5) {
      const dx = Math.abs(x);
      const centerOffset = halfW * 0.4;
      const dist = Math.sqrt((dx - centerOffset) ** 2 + 0);
      y = archHeight * 0.55 + Math.sqrt(Math.max(0, r * r - (dx - centerOffset) ** 2));
    }
    y = Math.max(0, y);
    return new THREE.Vector3(x, y, z);
  }

  for (let i = 0; i <= vaultSegments; i++) {
    const z = (i / vaultSegments) * SEGMENT_LENGTH;
    for (let j = 0; j <= archSegments; j++) {
      const t = j / archSegments;
      const p = getArchPoint(t, z);
      vertices.push(p.x, p.y, p.z);
      const n = new THREE.Vector3(p.x, p.y - archHeight * 0.5, 0).normalize();
      normals.push(n.x, n.y, n.z);
      uvs.push(t, i / vaultSegments);
    }
  }

  const indices = [];
  for (let i = 0; i < vaultSegments; i++) {
    for (let j = 0; j < archSegments; j++) {
      const a = i * (archSegments + 1) + j;
      const b = a + 1;
      const c = a + archSegments + 1;
      const d = c + 1;
      indices.push(a, c, b);
      indices.push(b, c, d);
    }
  }

  geo.setIndex(indices);
  geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
  geo.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
  geo.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
  geo.computeVertexNormals();

  const vault = new THREE.Mesh(geo, stoneMat);
  vault.rotation.x = Math.PI; // flip to be ceiling
  vault.position.y = HALLWAY_HEIGHT;
  group.add(vault);

  // Rib arches (decorative ribs)
  const ribMat = new THREE.MeshStandardMaterial({
    color: 0x2a2520, roughness: 0.9, metalness: 0.1
  });

  for (let z = 0; z <= SEGMENT_LENGTH; z += SEGMENT_LENGTH / 2) {
    const ribCurve = new THREE.CatmullRomCurve3([
      new THREE.Vector3(-halfW, HALLWAY_HEIGHT, z),
      new THREE.Vector3(-halfW * 0.7, HALLWAY_HEIGHT + archHeight * 0.4, z),
      new THREE.Vector3(0, HALLWAY_HEIGHT + archHeight * 0.75, z),
      new THREE.Vector3(halfW * 0.7, HALLWAY_HEIGHT + archHeight * 0.4, z),
      new THREE.Vector3(halfW, HALLWAY_HEIGHT, z)
    ]);
    const ribGeo = new THREE.TubeGeometry(ribCurve, 20, 0.15, 8, false);
    group.add(new THREE.Mesh(ribGeo, ribMat));
  }

  // Diagonal ribs
  const diagRibCurve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-halfW, HALLWAY_HEIGHT, 0),
    new THREE.Vector3(-halfW * 0.5, HALLWAY_HEIGHT + archHeight * 0.5, SEGMENT_LENGTH * 0.25),
    new THREE.Vector3(0, HALLWAY_HEIGHT + archHeight * 0.78, SEGMENT_LENGTH * 0.5),
    new THREE.Vector3(halfW * 0.5, HALLWAY_HEIGHT + archHeight * 0.5, SEGMENT_LENGTH * 0.75),
    new THREE.Vector3(halfW, HALLWAY_HEIGHT, SEGMENT_LENGTH)
  ]);
  group.add(new THREE.Mesh(new THREE.TubeGeometry(diagRibCurve, 30, 0.12, 8, false), ribMat));

  const diagRibCurve2 = diagRibCurve.clone();
  const mirrorMatrix = new THREE.Matrix4().makeScale(-1, 1, 1);
  diagRibCurve2.applyMatrix4(mirrorMatrix);
  group.add(new THREE.Mesh(new THREE.TubeGeometry(diagRibCurve2, 30, 0.12, 8, false), ribMat));

  return group;
}

function createPillar(x, z) {
  const group = new THREE.Group();
  const stoneMat = new THREE.MeshStandardMaterial({
    color: 0x3a3530, roughness: 0.9, metalness: 0.05
  });

  // Main shaft
  const shaftGeo = new THREE.CylinderGeometry(0.35, 0.4, HALLWAY_HEIGHT, 12);
  group.add(new THREE.Mesh(shaftGeo, stoneMat));

  // Fluting
  for (let i = 0; i < 12; i++) {
    const angle = (i / 12) * Math.PI * 2;
    const fluteGeo = new THREE.BoxGeometry(0.04, HALLWAY_HEIGHT * 0.9, 0.04);
    const flute = new THREE.Mesh(fluteGeo, new THREE.MeshStandardMaterial({
      color: 0x252018, roughness: 0.95
    }));
    flute.position.set(Math.cos(angle) * 0.32, 0, Math.sin(angle) * 0.32);
    flute.rotation.y = angle;
    group.add(flute);
  }

  // Capital (Gothic style)
  const capGeo = new THREE.CylinderGeometry(0.55, 0.35, 0.6, 12);
  const cap = new THREE.Mesh(capGeo, stoneMat);
  cap.position.y = HALLWAY_HEIGHT / 2 + 0.3;
  group.add(cap);

  // Capital molding
  const moldGeo = new THREE.TorusGeometry(0.5, 0.08, 8, 12);
  const mold = new THREE.Mesh(moldGeo, stoneMat);
  mold.position.y = HALLWAY_HEIGHT / 2 + 0.6;
  mold.rotation.x = Math.PI / 2;
  group.add(mold);

  // Base
  const baseGeo = new THREE.CylinderGeometry(0.55, 0.6, 0.4, 12);
  const base = new THREE.Mesh(baseGeo, stoneMat);
  base.position.y = -HALLWAY_HEIGHT / 2 + 0.2;
  group.add(base);

  const baseStepGeo = new THREE.CylinderGeometry(0.65, 0.55, 0.15, 12);
  const baseStep = new THREE.Mesh(baseStepGeo, stoneMat);
  baseStep.position.y = -HALLWAY_HEIGHT / 2 + 0.075;
  group.add(baseStep);

  group.position.set(x, HALLWAY_HEIGHT / 2, z);
  return group;
}

function createWindow(z, side, patternType, seed) {
  const group = new THREE.Group();
  const x = side * (HALLWAY_WIDTH / 2 + 0.01);

  // Stained glass pane
  const glassTex = createStainedGlassTexture(patternType, seed);
  const glassGeo = new THREE.PlaneGeometry(3, 5.5, 1, 1);
  const glassMat = new THREE.MeshBasicMaterial({
    map: glassTex, transparent: true, opacity: 0.95,
    side: THREE.DoubleSide
  });
  const glass = new THREE.Mesh(glassGeo, glassMat);
  glass.position.set(x, HALLWAY_HEIGHT * 0.75, z + SEGMENT_LENGTH / 2);
  glass.rotation.y = Math.PI / 2 * side;
  group.add(glass);

  // Emissive glow behind glass
  const glowMat = new THREE.MeshBasicMaterial({
    color: 0xffeedd, transparent: true, opacity: 0.4,
    side: THREE.DoubleSide
  });
  const glow = new THREE.Mesh(glassGeo.clone(), glowMat);
  glow.position.set(x - side * 0.1, HALLWAY_HEIGHT * 0.75, z + SEGMENT_LENGTH / 2);
  glow.rotation.y = Math.PI / 2 * side;
  group.add(glow);

  // Window arch frame
  const frameMat = new THREE.MeshStandardMaterial({
    color: 0x2a2520, roughness: 0.9, metalness: 0.1
  });
  const archCurve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(0, 0, -1.5),
    new THREE.Vector3(0, 2, -1.2),
    new THREE.Vector3(0, 3.5, -0.5),
    new THREE.Vector3(0, 4.5, 0.5),
    new THREE.Vector3(0, 3.5, 1.5),
    new THREE.Vector3(0, 2, 1.2),
    new THREE.Vector3(0, 0, 1.5),
  ]);
  // Actually let's make a proper pointed arch frame
  const frameShape = new THREE.Shape();
  const fw = 1.5, fh = 5.5;
  frameShape.moveTo(-fw, -fh / 2);
  frameShape.lineTo(-fw, fh / 2 - fw);
  // pointed arch
  const r = fw * 1.2;
  const archStartY = fh / 2 - fw;
  const centerX = -fw * 0.3;
  frameShape.absarc(centerX, archStartY + r - fw * 0.5, r, Math.PI * 0.75, Math.PI * 1.15, false);
  frameShape.absarc(fw * 0.3, archStartY + r - fw * 0.5, r, Math.PI * 1.15, Math.PI * 0.25, false);
  frameShape.lineTo(fw, fh / 2 - fw);
  frameShape.lineTo(fw, -fh / 2);
  frameShape.lineTo(-fw, -fh / 2);

  const holeShape = new THREE.Shape();
  const hw = 1.3, hh = 5.2;
  holeShape.moveTo(-hw, -hh / 2);
  holeShape.lineTo(-hw, hh / 2 - hw);
  const hr = hw * 1.2;
  const hArchStartY = hh / 2 - hw;
  holeShape.absarc(-hw * 0.3, hArchStartY + hr - hw * 0.5, hr, Math.PI * 0.75, Math.PI * 1.15, false);
  holeShape.absarc(hw * 0.3, hArchStartY + hr - hw * 0.5, hr, Math.PI * 1.15, Math.PI * 0.25, false);
  holeShape.lineTo(hw, hh / 2 - hw);
  holeShape.lineTo(hw, -hh / 2);
  holeShape.lineTo(-hw, -hh / 2);
  frameShape.holes.push(holeShape);

  const frameGeo = new THREE.ExtrudeGeometry(frameShape, { depth: 0.3, bevelEnabled: false });
  const frame = new THREE.Mesh(frameGeo, frameMat);
  frame.position.set(x - side * 0.15, HALLWAY_HEIGHT * 0.75, z + SEGMENT_LENGTH / 2);
  frame.rotation.y = Math.PI / 2 * side;
  group.add(frame);

  return group;
}

function createLightShaft(z, side, color) {
  const group = new THREE.Group();
  const x = side * (HALLWAY_WIDTH / 2 - 0.5);

  // Volumetric cone (god ray)
  const coneGeo = new THREE.ConeGeometry(3.5, 10, 32, 1, true);
  const coneMat = new THREE.MeshBasicMaterial({
    color: color, transparent: true, opacity: 0.06,
    blending: THREE.AdditiveBlending, depthWrite: false,
    side: THREE.DoubleSide
  });
  const cone = new THREE.Mesh(coneGeo, coneMat);
  cone.position.set(x, HALLWAY_HEIGHT * 0.5, z + SEGMENT_LENGTH / 2);
  cone.rotation.z = Math.PI;
  cone.rotation.x = 0.3 * side;
  group.add(cone);

  // Inner brighter cone
  const innerConeGeo = new THREE.ConeGeometry(1.5, 9, 16, 1, true);
  const innerConeMat = new THREE.MeshBasicMaterial({
    color: color, transparent: true, opacity: 0.1,
    blending: THREE.AdditiveBlending, depthWrite: false,
    side: THREE.DoubleSide
  });
  const innerCone = new THREE.Mesh(innerConeGeo, innerConeMat);
  innerCone.position.copy(cone.position);
  innerCone.rotation.copy(cone.rotation);
  group.add(innerCone);

  // Dust particles in the light
  const particleCount = 80;
  const particleGeo = new THREE.BufferGeometry();
  const positions = new Float32Array(particleCount * 3);
  const sizes = new Float32Array(particleCount);
  for (let i = 0; i < particleCount; i++) {
    const t = Math.random();
    const r = Math.random() * 2.5 * (1 - t * 0.5);
    const angle = Math.random() * Math.PI * 2;
    positions[i * 3] = x + Math.cos(angle) * r + Math.sin(time) * 0.1;
    positions[i * 3 + 1] = HALLWAY_HEIGHT * 0.75 - t * 8;
    positions[i * 3 + 2] = z + SEGMENT_LENGTH / 2 + Math.sin(angle) * r;
    sizes[i] = 2 + Math.random() * 4;
  }
  particleGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
  particleGeo.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));

  const particleMat = new THREE.PointsMaterial({
    color: color, size: 0.08, transparent: true, opacity: 0.6,
    blending: THREE.AdditiveBlending, depthWrite: false,
    sizeAttenuation: true
  });
  const particles = new THREE.Points(particleGeo, particleMat);
  group.add(particles);
  group.userData.particles = particles;
  group.userData.particleGeo = particleGeo;

  // Colored light
  const light = new THREE.PointLight(color, 8, 15, 2);
  light.position.set(x, HALLWAY_HEIGHT * 0.75, z + SEGMENT_LENGTH / 2);
  group.add(light);

  // Colored spotlight on floor
  const spotLight = new THREE.SpotLight(color, 15, 20, Math.PI / 4, 0.5, 2);
  spotLight.position.set(x, HALLWAY_HEIGHT * 0.85, z + SEGMENT_LENGTH / 2);
  spotLight.target.position.set(x, 0, z + SEGMENT_LENGTH / 2);
  group.add(spotLight);
  group.add(spotLight.target);

  // Colored light pool on floor (bright patch)
  const poolGeo = new THREE.PlaneGeometry(5, 5);
  const poolMat = new THREE.MeshBasicMaterial({
    color: color, transparent: true, opacity: 0.15,
    blending: THREE.AdditiveBlending, depthWrite: false,
    side: THREE.DoubleSide
  });
  const pool = new THREE.Mesh(poolGeo, poolMat);
  pool.rotation.x = -Math.PI / 2;
  pool.position.set(x, 0.01, z + SEGMENT_LENGTH / 2);
  group.add(pool);

  group.userData.color = color;
  group.userData.baseOpacity = 0.06;

  return group;
}

function createSegment(index) {
  const group = new THREE.Group();
  const z = 0;
  const stoneTex = createStoneTexture();

  // Side walls
  const wallMat = new THREE.MeshStandardMaterial({
    map: stoneTex, roughness: 0.95, metalness: 0.05, color: 0x3a3530
  });
  for (let side = -1; side <= 1; side += 2) {
    const wallGeo = new THREE.PlaneGeometry(SEGMENT_LENGTH, HALLWAY_HEIGHT, 1, 1);
    const wall = new THREE.Mesh(wallGeo, wallMat);
    wall.position.set(side * HALLWAY_WIDTH / 2, HALLWAY_HEIGHT / 2, z + SEGMENT_LENGTH / 2);
    wall.rotation.y = -Math.PI / 2 * side;
    group.add(wall);

    // Wall buttresses
    const buttressGeo = new THREE.BoxGeometry(0.8, HALLWAY_HEIGHT, 1.2);
    const buttress = new THREE.Mesh(buttressGeo, wallMat);
    buttress.position.set(side * (HALLWAY_WIDTH / 2 + 0.4), HALLWAY_HEIGHT / 2, z + SEGMENT_LENGTH / 2);
    group.add(buttress);
  }

  // Back wall
  const backWallGeo = new THREE.PlaneGeometry(HALLWAY_WIDTH, HALLWAY_HEIGHT);
  const backWall = new THREE.Mesh(backWallGeo, wallMat);
  backWall.position.set(0, HALLWAY_HEIGHT / 2, z + SEGMENT_LENGTH);
  group.add(backWall);

  // Pillars
  for (let side = -1; side <= 1; side += 2) {
    group.add(createPillar(side * (HALLWAY_WIDTH / 2 - 0.7), z));
    group.add(createPillar(side * (HALLWAY_WIDTH / 2 - 0.7), z + SEGMENT_LENGTH));
  }

  // Vaulted ceiling
  group.add(createVaultedCeilingGeometry());

  // Stained glass windows
  const patterns = ['diamond', 'rose', 'voronoi'];
  const patternType = patterns[index % patterns.length];
  const seed = index * 137 + 42;
  for (let side = -1; side <= 1; side += 2) {
    group.add(createWindow(z, side, patternType, seed + side));
  }

  // Light shafts
  const lightColors = [0xff6644, 0x44aaff, 0xffcc44, 0x88ff44, 0xff44aa, 0x44ffcc];
  const lightColor = lightColors[index % lightColors.length];
  for (let side = -1; side <= 1; side += 2) {
    const shaft = createLightShaft(z, side, lightColor);
    group.add(shaft);
    LIGHT_SHAFTS.push(shaft);
  }

  // Decorative floor molding along walls
  const moldingMat = new THREE.MeshStandardMaterial({
    color: 0x4a4035, roughness: 0.8, metalness: 0.15
  });
  for (let side = -1; side <= 1; side += 2) {
    const moldingGeo = new THREE.BoxGeometry(SEGMENT_LENGTH, 0.15, 0.3);
    const molding = new THREE.Mesh(moldingGeo, moldingMat);
    molding.position.set(side * (HALLWAY_WIDTH / 2 - 0.15), 0.075, z + SEGMENT_LENGTH / 2);
    group.add(molding);
  }

  // Gothic wall niches between pillars
  for (let side = -1; side <= 1; side += 2) {
    const nicheGeo = new THREE.SphereGeometry(0.8, 16, 16, 0, Math.PI, 0, Math.PI / 2);
    const niche = new THREE.Mesh(nicheGeo, wallMat);
    niche.position.set(side * (HALLWAY_WIDTH / 2 - 0.79), 1.5, z + SEGMENT_LENGTH / 2);
    niche.rotation.y = Math.PI / 2 * side;
    group.add(niche);

    // Torch/brazier in niche
    const brazierMat = new THREE.MeshStandardMaterial({
      color: 0x5a4a30, roughness: 0.7, metalness: 0.3
    });
    const brazierGeo = new THREE.CylinderGeometry(0.15, 0.2, 0.3, 8);
    const brazier = new THREE.Mesh(brazierGeo, brazierMat);
    brazier.position.set(side * (HALLWAY_WIDTH / 2 - 0.79), 1.1, z + SEGMENT_LENGTH / 2);
    group.add(brazier);

    // Torch flame light
    const flameLight = new THREE.PointLight(0xff8844, 2, 8, 2);
    flameLight.position.set(side * (HALLWAY_WIDTH / 2 - 0.79), 1.5, z + SEGMENT_LENGTH / 2);
    group.add(flameLight);

    // Flame glow
    const flameGlowGeo = new THREE.SphereGeometry(0.12, 8, 8);
    const flameGlowMat = new THREE.MeshBasicMaterial({
      color: 0xffaa44, transparent: true, opacity: 0.8
    });
    const flameGlow = new THREE.Mesh(flameGlowGeo, flameGlowMat);
    flameGlow.position.copy(flameLight.position);
    flameGlow.position.y += 0.15;
    group.add(flameGlow);
    group.userData.flames = group.userData.flames || [];
    group.userData.flames.push({ light: flameLight, glow: flameGlow, baseY: flameLight.position.y });
  }

  return group;
}

// ── Floor ──
function createFloor() {
  const checkeredTex = createCheckeredTexture();
  checkeredTex.repeat.set(4, 4);

  const floorGeo = new THREE.PlaneGeometry(HALLWAY_WIDTH, 2000, 1, 1);
  const floorMat = new THREE.MeshPhysicalMaterial({
    map: checkeredTex,
    roughness: 0.15,
    metalness: 0.85,
    clearcoat: 1.0,
    clearcoatRoughness: 0.1,
    reflectivity: 1,
    color: 0xffffff,
    envMapIntensity: 0.5
  });

  const floor = new THREE.Mesh(floorGeo, floorMat);
  floor.rotation.x = -Math.PI / 2;
  floor.position.y = 0;
  return floor;
}

// ── Init ──
function init() {
  clock = new THREE.Clock();

  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x050408);
  scene.fog = new THREE.FogExp2(0x050408, 0.035);

  camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(0, cameraYOffset, 0);

  renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 0.8;
  document.body.appendChild(renderer.domElement);

  // Environment map for reflections (simple procedural)
  const pmremGenerator = new THREE.PMREMGenerator(renderer);
  const envScene = new THREE.Scene();
  envScene.background = new THREE.Color(0x111118);
  const envLight1 = new THREE.DirectionalLight(0xffeedd, 1);
  envLight1.position.set(5, 10, 5);
  envScene.add(envLight1);
  const envLight2 = new THREE.DirectionalLight(0x4488ff, 0.5);
  envLight2.position.set(-5, 5, -5);
  envScene.add(envLight2);
  const envRT = pmremGenerator.fromScene(envScene);
  scene.environment = envRT.texture;
  pmremGenerator.dispose();

  // Floor
  const floor = createFloor();
  scene.add(floor);

  // Ambient light (very dim)
  const ambient = new THREE.AmbientLight(0x1a1520, 0.3);
  scene.add(ambient);

  // Initialize segment pool
  for (let i = 0; i < NUM_SEGMENTS; i++) {
    const segment = createSegment(i);
    segment.position.z = i * SEGMENT_LENGTH;
    scene.add(segment);
    SEGMENT_POOL.push({ mesh: segment, index: i });
  }

  // Events
  window.addEventListener('resize', onResize);
  window.addEventListener('mousemove', onMouseMove);
}

function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function onMouseMove(e) {
  mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
  mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
}

// ── Animation ──
function animate() {
  requestAnimationFrame(animate);
  const delta = Math.min(clock.getDelta(), 0.05);
  time += delta;

  // Move camera forward
  camera.position.z += CAMERA_SPEED * delta;

  // Camera sway (breathing/head movement)
  camera.position.x = Math.sin(time * 0.5) * 0.15;
  camera.position.y = cameraYOffset + Math.sin(time * 0.8) * 0.05;

  // Look direction from mouse
  targetLookX += (mouseX - targetLookX) * 3 * delta;
  targetLookY += (mouseY * 0.5 - targetLookY) * 3 * delta;
  const lookTarget = new THREE.Vector3(
    targetLookX * 3,
    -targetLookY * 2 + 1.5,
    camera.position.z + 10
  );
  camera.lookAt(lookTarget);

  // Recycle segments
  const cameraZ = camera.position.z;
  for (const seg of SEGMENT_POOL) {
    if (seg.mesh.position.z + SEGMENT_LENGTH < cameraZ - SEGMENT_LENGTH) {
      // Find the farthest segment ahead
      let maxZ = -Infinity;
      let farthest = null;
      for (const other of SEGMENT_POOL) {
        if (other.mesh.position.z > maxZ) {
          maxZ = other.mesh.position.z;
          farthest = other;
        }
      }
      // Move this segment to just beyond the farthest
      seg.index++;
      seg.mesh.position.z = farthest.mesh.position.z + SEGMENT_LENGTH;
      // Rebuild with new index for new patterns
      scene.remove(seg.mesh);
      seg.mesh = createSegment(seg.index);
      seg.mesh.position.z = farthest.mesh.position.z + SEGMENT_LENGTH;
      scene.add(seg.mesh);
    }
  }

  // Animate torch flames
  for (const seg of SEGMENT_POOL) {
    if (seg.mesh.userData.flames) {
      for (const flame of seg.mesh.userData.flames) {
        const flicker = Math.sin(time * 12 + flame.baseY * 100) * 0.3 +
                        Math.sin(time * 17 + flame.baseY * 50) * 0.2;
        flame.light.intensity = 2 + flicker;
        flame.glow.scale.setScalar(0.8 + flicker * 0.5);
        flame.glow.position.y = flame.baseY + 0.15 + flicker * 0.05;
      }
    }
  }

  // Animate light shafts (subtle pulsing)
  for (const shaft of LIGHT_SHAFTS) {
    const pulse = Math.sin(time * 1.5 + shaft.position.z * 0.1) * 0.15 + 1;
    for (const child of shaft.children) {
      if (child.material && child.material.opacity !== undefined && child.material.opacity < 0.5) {
        child.material.opacity = (child === shaft.children[0] ? shaft.userData.baseOpacity : 0.1) * pulse;
      }
    }
    // Animate dust particles
    if (shaft.userData.particleGeo) {
      const pos = shaft.userData.particleGeo.attributes.position;
      for (let i = 0; i < pos.count; i++) {
        pos.array[i * 3 + 1] -= delta * 0.5; // drift down
        pos.array[i * 3] += Math.sin(time * 2 + i) * delta * 0.3;
        pos.array[i * 3 + 2] += Math.cos(time * 1.5 + i) * delta * 0.2;
        // Reset if fallen too far
        if (pos.array[i * 3 + 1] < -2) {
          pos.array[i * 3 + 1] = HALLWAY_HEIGHT * 0.75;
        }
      }
      pos.needsUpdate = true;
    }
  }

  renderer.render(scene, camera);
}

init();
animate();
</script>
</body>
</html>

This creates a fully self-contained endless Gothic hallway with:

  • Vaulted ribbed ceiling — pointed arch geometry with decorative stone ribs crossing diagonally
  • Fluted stone pillars with Gothic capitals and stepped bases lining both sides
  • Glossy checkered marble floor using MeshPhysicalMaterial with clearcoat, high metalness, and an environment map for reflections
  • Three stained glass window patterns cycling per segment: diamond grids, rose wheels (concentric petal rings), and Voronoi cells with lead-line borders, all shaped as pointed Gothic arches
  • Volumetric god rays — additive-blended cones with inner bright cores, drifting dust particles, colored point/spot lights, and glowing light pools on the floor
  • Flickering torch braziers in wall niches between pillars
  • Dark vignetting via CSS radial gradient overlay
  • Smooth endless movement — camera drifts forward with breathing sway; hallway segments recycle behind you with new pattern seeds
  • Mouse look — move your mouse to steer the view; fog fades the distant corridor into darkness

Runs of DedeProGames GRM-Corridor on huggingface.co

0
Total runs
0
24-hour runs
0
3-day runs
0
7-day runs
0
30-day runs

More Information About GRM-Corridor huggingface.co Model

GRM-Corridor huggingface.co

GRM-Corridor huggingface.co is an AI model on huggingface.co that provides GRM-Corridor's model effect (), which can be used instantly with this DedeProGames GRM-Corridor model. huggingface.co supports a free trial of the GRM-Corridor model, and also provides paid use of the GRM-Corridor. Support call GRM-Corridor model through api, including Node.js, Python, http.

DedeProGames GRM-Corridor online free

GRM-Corridor huggingface.co is an online trial and call api platform, which integrates GRM-Corridor's modeling effects, including api services, and provides a free online trial of GRM-Corridor, you can try GRM-Corridor online for free by clicking the link below.

DedeProGames GRM-Corridor online free url in huggingface.co:

https://huggingface.co/DedeProGames/GRM-Corridor

GRM-Corridor install

GRM-Corridor is an open source model from GitHub that offers a free installation service, and any user can find GRM-Corridor on GitHub to install. At the same time, huggingface.co provides the effect of GRM-Corridor install, users can directly use GRM-Corridor installed effect in huggingface.co for debugging and trial. It also supports api for free installation.

GRM-Corridor install url in huggingface.co:

https://huggingface.co/DedeProGames/GRM-Corridor

Url of GRM-Corridor

Provider of GRM-Corridor huggingface.co

DedeProGames
ORGANIZATIONS

Other API from DedeProGames

huggingface.co

Total runs: 1.0K
Run Growth: 1.0K
Growth Rate: 100.00%
Updated:September 07 2026
huggingface.co

Total runs: 300
Run Growth: 292
Growth Rate: 97.33%
Updated:September 28 2026
huggingface.co

Total runs: 232
Run Growth: 224
Growth Rate: 96.55%
Updated:September 28 2026