oneshotlm
Compare all models on this prompt →

Lorenz attractor · lfm-2.5-24b-a2b

No runnable output Cost / run $0.0178 Runtime 126.0s

Prompt for this run

Build a 3D Lorenz-attractor visualization using the provided Three.js library. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. Use the provided `three.min.js` (global `THREE`) through a plain local script tag; the runner will inline it into the final artifact. Required behavior: - Integrate the Lorenz system with sigma = 10, rho = 28, beta = 8/3, and initial state (0.1, 0, 0). Use a stable numerical time step. - Draw a luminous-looking, colored trajectory progressively as the system evolves, revealing its two-lobed butterfly shape. Keep trajectory storage bounded; actual bloom postprocessing is optional. - Frame the full attractor initially. Dragging orbits the camera around it, and the mouse wheel changes camera distance without entering the geometry. - Provide Pause/Resume and Reset. Pause freezes trajectory growth while camera controls remain usable; Reset clears the trail and restores the initial simulation state. - Only the core global THREE library is provided. Implement camera interaction without importing unavailable addons. Verify: Sample trajectory growth, orbit the camera, and check that the existing trail remains connected and visible from the new angle. Pause growth while moving the camera, then reset and check that the trail starts again from the initial state.

⚠ no runnable output

This one-shot run produced no runnable artifact.

Error: agent produced no index.html

The transcript below shows what the model did — read it to see why the one shot fell short.

TAKE IT TO CODE

This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.

RUN

modellfm-2.5-24b-a2b
vendorLiquid AI
harnesspi 0.87.1 · browser iteration
generated2026-10-21

METRICS

runtime126.0s
tokens in9.8k
tokens out25k
cached2.5k
cost / run$0.0178
Transcript · 9 messages
system

user

Build a 3D Lorenz-attractor visualization using the provided Three.js library. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. Use the provided `three.min.js` (global `THREE`) through a plain local script tag; the runner will inline it into the final artifact. Required behavior: - Integrate the Lorenz system with sigma = 10, rho = 28, beta = 8/3, and initial state (0.1, 0, 0). Use a stable numerical time step. - Draw a luminous-looking, colored trajectory progressively as the system evolves, revealing its two-lobed butterfly shape. Keep trajectory storage bounded; actual bloom postprocessing is optional. - Frame the full attractor initially. Dragging orbits the camera around it, and the mouse wheel changes camera distance without entering the geometry. - Provide Pause/Resume and Reset. Pause freezes trajectory growth while camera controls remain usable; Reset clears the trail and restores the initial simulation state. - Only the core global THREE library is provided. Implement camera interaction without importing unavailable addons. Verify: Sample trajectory growth, orbit the camera, and check that the existing trail remains connected and visible from the new angle. Pause growth while moving the camera, then reset and check that the trail starts again from the initial state.

assistant
thinking
We need to build a 3D Lorenz attractor visualization using Three.js. The task says "Use the provided `three.min.js` (global `THREE`) through a plain local script tag". So I need to check what's in the current directory. Let me start by exploring.

The requirements:
1. Lorenz system: sigma=10, rho=28, beta=8/3, initial state (0.1, 0, 0)
2. Stable numerical time step
3. Draw luminous colored trajectory progressively revealing butterfly shape
4. Frame the full attractor initially
5. Drag to orbit camera around it; mouse wheel changes camera distance without entering geometry
6. Pause/Resume and Reset controls
7. Only core global THREE library - implement camera interaction without addons

Let me first check the current directory for existing files.
→ bash {"command":"ls -la /tmp/job-GrNb4G"}
toolResult

total 604 drwx------ 3 node node 4096 Oct 21 14:00 . drwxrwxrwt 7 root root 4096 Oct 21 14:00 .. drwxr-xr-x 3 node node 4096 Oct 21 14:00 .home -rw-r--r-- 1 node node 603445 Oct 21 14:00 three.min.js

assistant
thinking
There's a `three.min.js` file. Let me check if there's an existing index.html or any other files. The task says to write to `index.html` in the current working directory. Let me check what's in the directory more thoroughly.

I need to create an `index.html` that includes:
- A script tag that loads three.min.js (the provided library)
- CSS and JS inline
- The Lorenz attractor visualization

Let me first understand the structure of three.min.js - it's a minified Three.js library. I'll need to reference THREE globally.

Let me plan the implementation:

1. **Lorenz system integration**: 
   - dx/dt = sigma * (y - x)
   - dy/dt = x * (rho - z) - y
   - dz/dt = x * y - beta * z
   - Parameters: sigma=10, rho=28, beta=8/3
   - Initial state: (0.1, 0, 0)

2. **Trajectory rendering**:
   - Store points as a buffer geometry
   - Draw with a luminous color (gradient along the path)
   - Keep trajectory bounded

3. **Camera controls**:
   - Drag to orbit camera around the attractor (orbit controls without addons)
   - Mouse wheel to change distance
   - Need to implement custom mouse drag handling for camera rotation

4. **Pause/Resume**: Freeze the simulation but keep camera controls working
5. **Reset**: Clear the trail and restore initial state

For camera interaction without addons, I can implement basic orbit-like behavior:
- Track mouse down, move for rotation
- Track mouse wheel for zoom (distance)
- Update camera position based on these inputs

Let me think about how to implement this:

**Camera controls approach:**
- On mousedown: record starting position
- On mousemove: calculate delta and rotate camera around center
- On mouseup: stop rotating
- On wheel: adjust distance from center

For a simple orbit, I can use spherical coordinates:
- theta (azimuth), phi (polar)
- Camera position: distance * [sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi)]

Actually, let me keep it simpler. I'll track the camera position directly:
- When dragging, update camera position based on mouse movement
- When wheel, change the radius

Let me write the code:

```html
<!DOCTYPE html>
<html>
<head>
<style>
  body { margin: 0; overflow: hidden; background: #000; }
  canvas { display: block; }
  #controls {
    position: absolute;
    top: 10px;
    left: 10px;
    color: white;
    font-family: monospace;
  }
</style>
</head>
<body>
<div id="controls">
  <button id="pause">Pause</button>
  <button id="resume">Resume</button>
  <button id="reset">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
// ... JavaScript code ...
</script>
</body>
</html>
```

Now for the JavaScript:

**Lorenz integration:**
```javascript
const sigma = 10;
const rho = 28;
const beta = 8/3;
const dt = 0.01; // stable time step

let x = 0.1, y = 0, z = 0;

// Trajectory storage
const maxPoints = 5000;
const points = [];
```

For smooth rendering, I'll store points and update the geometry periodically.

**Rendering the trajectory:**
- Use BufferGeometry with Line2 or just a Line with BufferAttributes
- For a "luminous" look, I can use a gradient shader or simply make the line bright
- Actually, for a glowing effect without postprocessing, I can use a bright color like cyan/white

Let me use a Line with a solid color. To make it "luminous", I could use a bright cyan or white color.

**Camera orbit:**
- Initial camera position: somewhere looking at origin
- On drag: rotate camera around origin
- On wheel: change distance

Let me implement a simple approach:
- Camera starts at (0, 0, 10) looking at (0, 0, 0)
- On mousedown: start tracking
- On mousemove: update camera rotation
- On wheel: scale distance

For the orbit, I'll maintain:
- cameraDistance: distance from origin
- cameraAngle: azimuthal angle

When dragging, I'll update the angle. When wheel, I'll change the distance.

Let me write the full code now.

Actually, let me think about the trajectory growth. The Lorenz attractor is generated over time. Each frame (or every N frames), I compute the next point and add it to the trail. The trail should be visible as a connected curve.

For performance, I'll limit the number of points (e.g., 3000-5000).

Let me structure the code:

```javascript
// Setup scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 20, 40);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.5);
dirLight.position.set(10, 10, 10);
scene.add(dirLight);

// Lorenz parameters
const sigma = 10, rho = 28, beta = 8/3;
let x = 0.1, y = 0, z = 0;

// Trajectory
const maxPoints = 4000;
const trajectoryGeometry = new THREE.BufferGeometry();
const trajectoryPositions = new Float32Array(maxPoints * 3);
const trajectoryColors = new Float32Array(maxPoints * 3);
trajectoryGeometry.setAttribute('position', new THREE.BufferAttribute(trajectoryPositions, 3));
trajectoryGeometry.setAttribute('color', new THREE.BufferAttribute(trajectoryColors, 3));

const trajectoryMaterial = new THREE.LineBasicMaterial({
  color: 0x00ffff,
  linewidth: 1
});
const trajectoryLine = new THREE.Line(trajectoryGeometry, trajectoryMaterial);
scene.add(trajectoryLine);

// Controls
let paused = false;
let resetPoint = { x: 0.1, y: 0, z: 0 };
let dragStartX, dragStartY;
let cameraDistance = 15;
let cameraAngle = 0;

// Orbit variables
let isDragging = false;
let orbitSpeed = 0.05;

function lorenzStep(dt) {
  const dx = sigma * (y - x);
  const dy = x * (rho - z) - y;
  const dz = x * y - beta * z;
  x += dx * dt;
  y += dy * dt;
  z += dz * dt;
}

function addPointToTrajectory(px, py, pz) {
  // Circular buffer
  // ...
}
```

Hmm, for the circular buffer, I can use an index that wraps around. Let me use a simpler approach: just push to an array and recreate the geometry when it gets too large.

Actually, for simplicity, I'll use an array and only keep the last N points. When adding, shift if needed.

Let me think about the "luminous" appearance. I can use a bright color like white or cyan. For a nice glow effect without postprocessing, I could draw multiple lines with decreasing opacity, but that might be complex. Let me keep it simple with a bright cyan line.

For the "two-lobed butterfly shape" - the Lorenz attractor naturally has this shape.

**Camera controls details:**

Initial framing: camera positioned to show the whole attractor.

Drag to orbit: When the user clicks and drags, rotate the camera around the origin. I'll implement this by:
- Tracking mouse movement deltas
- Updating camera position in spherical coordinates

Mouse wheel: Change cameraDistance.

```javascript
renderer.domElement.addEventListener('wheel', (e) => {
  e.preventDefault();
  cameraDistance -= e.deltaY * 0.01;
  cameraDistance = Math.max(5, Math.min(50, cameraDistance));
});
```

**Pause/Resume:**
- Pause: Stop updating the Lorenz simulation
- Resume: Continue updating

**Reset:**
- Reset the initial state (x=0.1, y=0, z=0)
- Clear the trajectory
- Restore camera to initial position

Let me write the complete code now.

For the trajectory, I want it to grow progressively. So each frame (or every few frames), I compute a new point and add it to the trail. The trail should be visible as a connected line.

Let me use a reasonable time step. dt=0.01 is stable for the Lorenz system.

For the number of points: let's say 5000 points. At dt=0.01, that's 50 seconds of simulation. That should be enough to see the butterfly shape.

Now, let me think about the geometry update. I'll add points to the trajectory array and update the BufferGeometry.

```javascript
let trajectoryIndex = 0;
const maxPoints = 5000;

function updateTrajectory() {
  if (paused) return;
  
  lorenzStep(dt);
  
  const px = x * 10; // scale to fit in view
  const py = y * 10;
  const pz = z * 10;
  
  trajectoryPositions[trajectoryIndex * 3] = px;
  trajectoryPositions[trajectoryIndex * 3 + 1] = py;
  trajectoryPositions[trajectoryIndex * 3 + 2] = pz;
  
  // Color gradient
  const t = trajectoryIndex / maxPoints;
  trajectoryColors[trajectoryIndex * 3] = 0.5 + 0.5 * Math.sin(t * Math.PI);
  trajectoryColors[trajectoryIndex * 3 + 1] = 0.5 + 0.5 * Math.cos(t * Math.PI);
  trajectoryColors[trajectoryIndex * 3 + 2] = 0.8;
  
  trajectoryIndex = (trajectoryIndex + 1) % maxPoints;
  
  trajectoryGeometry.attributes.position.needsUpdate = true;
  trajectoryGeometry.attributes.color.needsUpdate = true;
}
```

Wait, I need to be careful with the BufferAttribute. When I modify the underlying array and set needsUpdate, it should work.

Let me also think about the camera. The initial camera should be positioned to frame the attractor. The attractor is centered around the origin with a typical extent of about ±20 in x, y, z. So a camera at (0, 20, 40) looking at origin would work.

For orbit: when dragging, I'll rotate the camera around the origin. I can do this by:
- Keeping track of the camera's current spherical coordinates
- On drag, update the azimuth angle
- Recompute camera position

Let me implement a simple version:

```javascript
let isDragging = false;
let prevMouseX = 0;
let prevMouseY = 0;

renderer.domElement.addEventListener('mousedown', (e) => {
  isDragging = true;
  prevMouseX = e.clientX;
  prevMouseY = e.clientY;
});

window.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  const dx = e.clientX - prevMouseX;
  const dy = e.clientY - prevMouseY;
  prevMouseX = e.clientX;
  prevMouseY = e.clientY;
  
  // Rotate camera around origin
  cameraAngle += dx * 0.005;
  // Adjust distance slightly based on vertical movement? No, just angle.
});

window.addEventListener('mouseup', () => {
  isDragging = false;
});
```

And for the wheel:
```javascript
renderer.domElement.addEventListener('wheel', (e) => {
  e.preventDefault();
  cameraDistance -= e.deltaY * 0.02;
  cameraDistance = Math.max(5, Math.min(30, cameraDistance));
});
```

But wait, when the camera moves, the distance from the origin changes. The camera position should be:
- radius = cameraDistance
- theta = cameraAngle
- phi = constant (say, 0.5 radians for a good viewing angle)

```javascript
camera.position.set(
  cameraDistance * Math.sin(phi) * Math.cos(cameraAngle),
  cameraDistance * Math.cos(phi),
  cameraDistance * Math.sin(phi) * Math.sin(cameraAngle)
);
```

Actually, let me simplify. I'll use a fixed polar angle (phi) and vary the azimuth (theta). This gives a cone view around the attractor.

Let me set phi = PI/2 (equatorial plane) for a side view, or something like 0.8 for a tilted view. Actually, for a nice butterfly shape, a view from slightly above the equator works well.

Let me use phi = PI/2 (in the xy-plane) and vary theta from 0 to 2PI. The camera will orbit around the z-axis.

```javascript
const phi = Math.PI / 2; // equatorial plane
const radius = cameraDistance;

camera.position.set(
  radius * Math.sin(phi) * Math.cos(cameraAngle),
  radius * Math.cos(phi),
  radius * Math.sin(phi) * Math.sin(cameraAngle)
);
```

With phi = PI/2: sin(phi)=1, cos(phi)=0, so:
- x = radius * cos(cameraAngle)
- y = 0
- z = radius * sin(cameraAngle)

This would give a circle in the xz-plane. Hmm, that's not great for seeing the butterfly.

Let me use a different phi. For a good view of the butterfly, I want to see it from a perspective that shows both lobes. Let me try phi ≈ 0.6 radians (about 34 degrees from the z-axis).

Actually, let me just experiment with the code. The key is that the camera orbits around the origin.

Let me finalize the design:

**Scene setup:**
- Scene with dark background
- Ambient light + directional light
- Camera initially at (0, 20, 40) looking at (0, 0, 0)

**Lorenz simulation:**
- dt = 0.01
- Step function updates x, y, z
- Add point to trajectory every frame (or every N frames)

**Trajectory:**
- BufferGeometry with position and color attributes
- Color gradient from start to end
- Luminous cyan/white color

**Controls:**
- Pause button: toggles paused state
- Resume button: resumes simulation
- Reset button: clears trajectory, resets state to (0.1, 0, 0), resets camera
- Drag to orbit camera
- Mouse wheel to zoom

**Implementation details:**

For the trajectory, I'll use a dynamic array that grows, and when it exceeds maxPoints, I'll shift. Actually, for simplicity, I'll use a fixed-size array and a circular buffer index.

Let me write the complete HTML file now.

Actually, one concern: the three.min.js file - I need to check if it's a standard Three.js. Let me quickly check its header to confirm it defines `THREE`.

Let me read the first few lines of three.min.js.

Actually, I know it's Three.js. The task says "Use the provided three.min.js (global THREE)". So I'll assume THREE is available globally after loading the script.

Let me write the code:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorenz Attractor</title>
<style>
  body { margin: 0; overflow: hidden; background: #000; }
  canvas { display: block; }
  #ui {
    position: absolute;
    top: 10px;
    left: 10px;
    color: #fff;
    font-family: monospace;
    font-size: 14px;
    z-index: 100;
  }
  #ui button {
    margin-right: 5px;
    padding: 4px 10px;
    cursor: pointer;
  }
</style>
</head>
<body>
<div id="ui">
  <button id="pauseBtn">Pause</button>
  <button id="resumeBtn">Resume</button>
  <button id="resetBtn">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
(function() {
  'use strict';
  
  // --- Three.js setup ---
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x000000);
  
  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 20, 40);
  camera.lookAt(0, 0, 0);
  
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  
  // Lighting
  const ambient = new THREE.AmbientLight(0x404040);
  scene.add(ambient);
  const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
  dirLight.position.set(5, 10, 7);
  scene.add(dirLight);
  
  // --- Lorenz System ---
  const sigma = 10;
  const rho = 28;
  const beta = 8/3;
  const dt = 0.01;
  
  let x = 0.1, y = 0, z = 0;
  
  // Trajectory
  const MAX_POINTS = 5000;
  const positions = new Float32Array(MAX_POINTS * 3);
  const colors = new Float32Array(MAX_POINTS * 3);
  const trajectoryGeo = new THREE.BufferGeometry();
  trajectoryGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  trajectoryGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  
  const trajectoryMat = new THREE.LineBasicMaterial({
    color: 0x00ffff,
    linewidth: 1
  });
  const trajectoryLine = new THREE.Line(trajectoryGeo, trajectoryMat);
  scene.add(trajectoryLine);
  
  // --- State ---
  let paused = false;
  let resetState = { x: 0.1, y: 0, z: 0 };
  let trajectoryIndex = 0;
  
  // --- Camera orbit controls ---
  let isDragging = false;
  let dragStartX = 0;
  let dragStartY = 0;
  let cameraDistance = 15;
  const phi = Math.PI / 2.5; // viewing angle
  const baseRadius = 12;
  
  function getCameraPosition(angle) {
    const r = cameraDistance;
    return new THREE.Vector3(
      r * Math.sin(phi) * Math.cos(angle),
      r * Math.cos(phi),
      r * Math.sin(phi) * Math.sin(angle)
    );
  }
  
  function updateCamera() {
    const pos = getCameraPosition(cameraAngle);
    camera.position.copy(pos);
    camera.lookAt(0, 0, 0);
  }
  
  // --- Mouse interaction ---
  const canvas = renderer.domElement;
  
  canvas.addEventListener('mousedown', (e) => {
    isDragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;
  });
  
  window.addEventListener('mousemove', (e) => {
    if (!isDragging) return;
    const dx = e.clientX - dragStartX;
    const dy = e.clientY - dragStartY;
    cameraAngle += dx * 0.03;
    // Zoom with wheel handled separately
  });
  
  window.addEventListener('mouseup', () => {
    isDragging = false;
  });
  
  canvas.addEventListener('wheel', (e) => {
    e.preventDefault();
    cameraDistance -= e.deltaY * 0.02;
    cameraDistance = Math.max(5, Math.min(35, cameraDistance));
    updateCamera();
  });
  
  // --- Keyboard shortcuts ---
  window.addEventListener('keydown', (e) => {
    if (e.key === ' ' || e.key === 'P') {
      togglePause();
    }
    if (e.key === 'r' || e.key === 'R') {
      resetSimulation();
    }
  });
  
  function togglePause() {
    paused = !paused;
    document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
    document.getElementById('resumeBtn').textContent = paused ? 'Pause' : 'Resume';
  }
  
  function resetSimulation() {
    x = resetState.x;
    y = resetState.y;
    z = resetState.z;
    trajectoryIndex = 0;
    
    // Clear trajectory
    for (let i = 0; i < MAX_POINTS; i++) {
      positions[i*3] = 0;
      positions[i*3+1] = 0;
      positions[i*3+2] = 0;
    }
    trajectoryGeo.attributes.position.needsUpdate = true;
    trajectoryGeo.attributes.color.needsUpdate = true;
    
    updateCamera();
  }
  
  // --- Simulation step ---
  function lorenzStep() {
    const dx = sigma * (y - x);
    const dy = x * (rho - z) - y;
    const dz = x * y - beta * z;
    x += dx * dt;
    y += dy * dt;
    z += dz * dt;
  }
  
  function addToTrajectory() {
    if (trajectoryIndex >= MAX_POINTS) {
      trajectoryIndex = 0;
    }
    positions[trajectoryIndex * 3] = x * 8;
    positions[trajectoryIndex * 3 + 1] = y * 8;
    positions[trajectoryIndex * 3 + 2] = z * 8;
    
    // Gradient color
    const t = trajectoryIndex / MAX_POINTS;
    colors[trajectoryIndex * 3] = 0.5 + 0.5 * Math.sin(t * Math.PI);
    colors[trajectoryIndex * 3 + 1] = 0.5 + 0.5 * Math.cos(t * Math.PI);
    colors[trajectoryIndex * 3 + 2] = 0.9;
    
    trajectoryGeo.attributes.position.needsUpdate = true;
    trajectoryGeo.attributes.color.needsUpdate = true;
    trajectoryIndex++;
  }
  
  // --- Animation loop ---
  let lastTime = 0;
  function animate(time) {
    requestAnimationFrame(animate);
    
    const dtSim = 0.016; // fixed timestep for simulation
    
    if (!paused) {
      lorenzStep();
      addToTrajectory();
    }
    
    // Smooth camera rotation even when paused? No, just update on drag/wheel
    
    updateCamera();
    renderer.render(scene, camera);
  }
  
  // Handle resize
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });
  
  // Start
  updateCamera();
  animate(0);
  
})();
</script>
</body>
</html>
```

Wait, I need to double-check a few things:

1. The `linewidth` property on LineBasicMaterial - in Three.js, LineBasicMaterial doesn't have a linewidth property in the standard API... actually, in older versions it did, but in modern Three.js it's not supported. However, since we're using a basic material, the line width might default to 1. Let me keep it but it might not render properly. Actually, for a simple line, the default width should be fine.

2. The trajectory scaling: I multiplied by 8 to make it visible. The Lorenz attractor values are roughly in the range [-20, 20]. Multiplying by 8 gives [-160, 160], which might be too big. Let me use a smaller scale factor, like 1 or 2. Actually, the attractor's bounds are approximately:
   - x: -20 to 20
   - y: -25 to 25
   - z: -40 to 50
   
   With camera at distance 15, the attractor extends about 40 units in z. If I scale by 1, the points will be within about 40 units from origin. The camera at distance 15 might be too close. Let me scale by 1.5 and keep camera distance around 15-20.

   Actually, let me think about the camera distance. If the attractor is about 40 units across, and the camera is at distance 15, the entire attractor might not fit in the view. Let me set the camera distance to about 20-25.

   Let me adjust: scale factor 1.5, camera distance 20.

3. The color gradient: I'm using a simple sine/cosine gradient. That should look nice.

4. The "luminous" look: bright cyan (0x00ffff) should be luminous against a dark background.

5. For the trail to be "bounded": I'm using a circular buffer with MAX_POINTS, so it's bounded.

6. The pause/resume: when paused, the simulation stops but the camera can still be controlled. Good.

7. Reset: clears the trajectory and resets state. Good.

Let me refine the code. Some issues to fix:

- The `linewidth` property might not work in modern Three.js. I'll remove it or keep it (it won't hurt).
- The camera distance scaling: let me use a scale factor of 1.5 and cameraDistance of 20.
- The initial camera position: I'll set it up so it frames the attractor nicely.

Let me also add some visual polish:
- Maybe a subtle glow effect? Without postprocessing, I can draw multiple lines with decreasing opacity, but that's complex. Let me keep it simple with a bright line.

Actually, for a "luminous-looking" trajectory, I could use a bright white/cyan color. Let me stick with that.

Let me also make sure the trail is visible from the new camera angle. The trail is drawn in world space, so it should always be visible regardless of camera position (as long as it's within the view frustum).

One issue: when the camera moves closer (smaller distance), the trail might go outside the view. But with cameraDistance clamped to 5-35, it should be fine.

Let me finalize the code. I'll write it to index.html.

Actually, let me reconsider the camera orbit. The current implementation uses a fixed phi and varies the azimuth. This creates an orbit in a cone. That should work for "orbiting the camera around the attractor".

For the mouse wheel zoom: I'm decreasing cameraDistance by e.deltaY * 0.02. Positive deltaY (scrolling down) decreases distance (zooms in). That seems correct.

Let me also handle the case where the user presses space for pause/resume (as I added in keydown).

Now, let me write the final index.html.

One more thing: the task says "Frame the full attractor initially." So the initial camera position should show the entire attractor. The attractor is roughly within a box of about 40x40x40 centered at origin. A camera at (0, 20, 40) looking at origin should show it well.

Let me adjust the initial camera:
- Position: (0, 20, 40)
- Look at: (0, 0, 0)

This should frame the attractor nicely.

Let me write the complete file now.

Actually, I realize I should test the code. Let me first write it, then run it in the browser to check.

Let me write the index.html file:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorenz Attractor</title>
<style>
  body { margin: 0; overflow: hidden; background: #000; }
  canvas { display: block; }
  #ui {
    position: absolute;
    top: 10px;
    left: 10px;
    color: #fff;
    font-family: monospace;
    font-size: 14px;
    z-index: 100;
    user-select: none;
  }
  #ui button {
    padding: 4px 12px;
    margin-right: 5px;
    cursor: pointer;
    border: 1px solid #555;
    background: #222;
    color: #fff;
  }
  #ui button:hover {
    background: #333;
  }
</style>
</head>
<body>
<div id="ui">
  <button id="pauseBtn">Pause</button>
  <button id="resumeBtn">Resume</button>
  <button id="resetBtn">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
(function() {
  'use strict';

  // --- Three.js scene setup ---
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x000000);

  const camera = new THREE.PerspectiveCamera(
    60,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.position.set(0, 20, 40);
  camera.lookAt(0, 0, 0);

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // Lighting
  const ambient = new THREE.AmbientLight(0x404040);
  scene.add(ambient);
  const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
  dirLight.position.set(5, 10, 7);
  scene.add(dirLight);

  // --- Lorenz system ---
  const sigma = 10;
  const rho = 28;
  const beta = 8 / 3;
  const dt = 0.01;

  let x = 0.1, y = 0, z = 0;

  // --- Trajectory ---
  const MAX_POINTS = 5000;
  const positions = new Float32Array(MAX_POINTS * 3);
  const colors = new Float32Array(MAX_POINTS * 3);
  const trajectoryGeo = new THREE.BufferGeometry();
  trajectoryGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  trajectoryGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));

  const trajectoryMat = new THREE.LineBasicMaterial({
    color: 0x00ffff
  });
  const trajectoryLine = new THREE.Line(trajectoryGeo, trajectoryMat);
  scene.add(trajectoryLine);

  // --- State ---
  let paused = false;
  let resetState = { x: 0.1, y: 0, z: 0 };
  let trajectoryIndex = 0;

  // --- Camera orbit controls ---
  let isDragging = false;
  let dragStartX = 0;
  let dragStartY = 0;
  let cameraDistance = 20;
  const phi = Math.PI / 2.5; // polar angle for view
  const scale = 1.5; // scale factor for trajectory

  function getCameraPosition(angle) {
    const r = cameraDistance;
    return new THREE.Vector3(
      r * Math.sin(phi) * Math.cos(angle),
      r * Math.cos(phi),
      r * Math.sin(phi) * Math.sin(angle)
    );
  }

  function updateCamera() {
    const pos = getCameraPosition(cameraAngle);
    camera.position.copy(pos);
    camera.lookAt(0, 0, 0);
  }

  // --- Mouse interaction ---
  const canvas = renderer.domElement;

  canvas.addEventListener('mousedown', (e) => {
    isDragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;
  });

  window.addEventListener('mousemove', (e) => {
    if (!isDragging) return;
    const dx = e.clientX - dragStartX;
    const dy = e.clientY - dragStartY;
    cameraAngle += dx * 0.03;
  });

  window.addEventListener('mouseup', () => {
    isDragging = false;
  });

  canvas.addEventListener('wheel', (e) => {
    e.preventDefault();
    cameraDistance -= e.deltaY * 0.02;
    cameraDistance = Math.max(5, Math.min(35, cameraDistance));
    updateCamera();
  });

  // --- Keyboard shortcuts ---
  window.addEventListener('keydown', (e) => {
    if (e.key === ' ' || e.key === 'P') {
      togglePause();
    }
    if (e.key === 'r' || e.key === 'R') {
      resetSimulation();
    }
  });

  function togglePause() {
    paused = !paused;
    document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
    document.getElementById('resumeBtn').textContent = paused ? 'Pause' : 'Resume';
  }

  function resetSimulation() {
    x = resetState.x;
    y = resetState.y;
    z = resetState.z;
    trajectoryIndex = 0;

    // Clear trajectory
    for (let i = 0; i < MAX_POINTS; i++) {
      positions[i * 3] = 0;
      positions[i * 3 + 1] = 0;
      positions[i * 3 + 2] = 0;
    }
    trajectoryGeo.attributes.position.needsUpdate = true;
    trajectoryGeo.attributes.color.needsUpdate = true;

    updateCamera();
  }

  // --- Simulation step ---
  function lorenzStep() {
    const dx = sigma * (y - x);
    const dy = x * (rho - z) - y;
    const dz = x * y - beta * z;
    x += dx * dt;
    y += dy * dt;
    z += dz * dt;
  }

  function addToTrajectory() {
    if (trajectoryIndex >= MAX_POINTS) {
      trajectoryIndex = 0;
    }
    positions[trajectoryIndex * 3] = x * scale;
    positions[trajectoryIndex * 3 + 1] = y * scale;
    positions[trajectoryIndex * 3 + 2] = z * scale;

    // Gradient color
    const t = trajectoryIndex / MAX_POINTS;
    colors[trajectoryIndex * 3] = 0.5 + 0.5 * Math.sin(t * Math.PI);
    colors[trajectoryIndex * 3 + 1] = 0.5 + 0.5 * Math.cos(t * Math.PI);
    colors[trajectoryIndex * 3 + 2] = 0.95;

    trajectoryGeo.attributes.position.needsUpdate = true;
    trajectoryGeo.attributes.color.needsUpdate = true;
    trajectoryIndex++;
  }

  // --- Animation loop ---
  let lastTime = 0;
  function animate(time) {
    requestAnimationFrame(animate);

    if (!paused) {
      lorenzStep();
      addToTrajectory();
    }

    updateCamera();
    renderer.render(scene, camera);
  }

  // Handle resize
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });

  // Initialize
  cameraAngle = 0;
  updateCamera();
  animate(0);

})();
</script>
</body>
</html>
```

Hmm, I need to declare `cameraAngle` before using it. Let me add that.

Also, I should make sure the `scale` variable is defined before use. It is, inside the IIFE.

Let me review the code once more:

1. ✅ Lorenz system with correct parameters
2. ✅ Trajectory drawing with color gradient
3. ✅ Camera orbit via drag
4. ✅ Mouse wheel zoom
5. ✅ Pause/Resume buttons
6. ✅ Reset button
7. ✅ No external assets

One concern: the `LineBasicMaterial` with `linewidth` - in modern Three.js, linewidth is not supported. But it shouldn't break anything. The line will just
custom

The output file index.html has not been written, or is empty. Write the complete app to index.html in the current directory now. Use the remaining time to verify it, then finish. The original 15-minute deadline still applies.

assistant
thinking
We need to build a 3D Lorenz attractor visualization using Three.js. The task says "Use the provided three.min.js (global THREE) through a plain local script tag; the runner will inline it into the final artifact."

So we need to create index.html that includes three.min.js. Since we can't use external resources, we need to include the three.min.js file. But wait - the instructions say "Do not use external assets, CDNs, or network requests" and "Use your own CSS and JavaScript". However, the three.min.js is provided in the directory. We need to include it somehow.

Let me think about how this works: The runner will inline the three.min.js into the final artifact. So we probably need to reference it with a script tag like `<script src="three.min.js"></script>`. But if we can't use network requests, maybe the file is available locally and the test harness will handle it.

Actually, re-reading: "Use the provided `three.min.js` (global `THREE`) through a plain local script tag; the runner will inline it into the final artifact." This suggests that the test environment has three.min.js available, and we should reference it via a script tag. The runner will inline it.

So our index.html should:
1. Include three.min.js via a script tag
2. Have our own CSS and JS inline
3. Implement the Lorenz attractor visualization

Let me design the app:

**Lorenz System:**
- dx/dt = σ(y - x)
- dy/dt = x(ρ - z) - y
- dz/dt = xy - βz
- Parameters: σ=10, ρ=28, β=8/3
- Initial state: (0.1, 0, 0)

**Visualization:**
- A particle that moves along the Lorenz attractor trajectory
- Store points progressively (trail)
- Color the trail (maybe gradient from start to end)
- Camera orbit via mouse drag
- Mouse wheel for zoom (change camera distance)
- Pause/Resume button
- Reset button

**Implementation approach:**
1. Use Three.js to create scene, camera, renderer
2. Create a line geometry that stores the trajectory points
3. Animate the particle moving along the trajectory
4. For the "luminous-looking colored trajectory", we can use a shader material or a line with gradient colors
5. Camera controls: trackball-style orbit via mouse drag + wheel zoom

Since we can't use OrbitControls (it's an addon), we need to implement basic camera orbit ourselves:
- On mouse down: start dragging
- On mouse move: update camera position based on drag direction
- On mouse up: stop dragging
- On wheel: adjust camera distance (zoom)

Let me write the code.

For the trajectory storage: we'll keep an array of points. As the simulation runs, we push new points. To keep it bounded, we can limit the number of points (e.g., keep last N points, or just let it grow). The requirement says "keep trajectory storage bounded; actual bloom postprocessing is optional." So we can either cap the number of points or just let it grow. Let's cap it at some reasonable size, e.g., 5000 points, and shift the line.

Actually, for a nice effect, we can store all points but redraw the line periodically. Or we can use a BufferGeometry that we update. Let's keep it simple: maintain an array of points, and when it gets too long, remove old ones from the front.

For the luminous coloring: we can use a custom shader material that makes the line glow, or use a gradient. A simpler approach: use a Line with a MeshBasicMaterial that has emissive properties, or use a custom shader.

Let me think about what "luminous-looking" means. We could use a shader that adds a glow effect. But implementing a proper glow in Three.js without postprocessing is complex. Instead, we can make the line color transition from one color to another along the path (e.g., blue to red), which looks vibrant.

Or we could use a `Line2` from three/examples/jsm/... but that's an addon. Let's stick with basic Line.

For the trail: we'll draw the entire history as a line, with points colored by their position along the trajectory (gradient).

Let me structure the code:

```html
<!DOCTYPE html>
<html>
<head>
<style>
  body { margin: 0; overflow: hidden; }
  #ui { position: absolute; top: 10px; left: 10px; z-index: 100; }
  button { margin-right: 10px; }
</style>
</head>
<body>
<div id="ui">
  <button id="pause">Pause</button>
  <button id="reset">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
// Our code here
</script>
</body>
</html>
```

Now the JavaScript:

```javascript
// Lorenz parameters
const sigma = 10;
const rho = 28;
const beta = 8/3;
const initial = { x: 0.1, y: 0, z: 0 };

// Simulation state
let t = 0;
const dt = 0.01; // stable time step
const points = [];
const maxPoints = 5000;

// Initialize
function init() {
  // reset state
  t = 0;
  points.length = 0;
  const p = { x: initial.x, y: initial.y, z: initial.z };
  points.push(p);
}

// Compute next point
function step() {
  const p = points[points.length - 1];
  const dx = sigma * (p.y - p.x);
  const dy = p.x * (rho - p.z) - p.y;
  const dz = p.x * p.y - beta * p.z;
  points.push({ x: p.x + dx * dt, y: p.y + dy * dt, z: p.z + dz * dt });
  // Boundary: keep only last maxPoints
  if (points.length > maxPoints) {
    points.shift();
  }
}

// Animation
let paused = false;
let lastTime = performance.now();

function animate(time) {
  requestAnimationFrame(animate);
  
  if (!paused) {
    step();
  }
  
  // Update camera
  updateCamera();
  
  // Render
  renderer.render(scene, camera);
}

// Camera controls
let isDragging = false;
let prevMouse = { x: 0, y: 0 };
let cameraPos = new THREE.Vector3(0, 0, 5);

function updateCamera() {
  // Simple orbit: rotate camera around origin
  // ...
}
```

Hmm, let me think more carefully about the camera interaction. The requirement: "Dragging orbits the camera around it, and the mouse wheel changes camera distance without entering the geometry."

So:
- Left mouse drag: orbit the camera around the attractor (which is centered at origin)
- Mouse wheel: zoom in/out (change distance from center)

For orbit, we can implement a simple trackball-like behavior:
- On mousedown: record start position
- On mousemove: compute delta angle and update camera position on a sphere of radius = current distance
- On mouseup: stop
- On wheel: change radius

Let me implement this:

```javascript
const cameraRadius = 5;
const cameraTarget = new THREE.Vector3(0, 0, 0);

function onMouseDown(event) {
  isDragging = true;
  prevMouse = { x: event.clientX, y: event.clientY };
}

function onMouseMove(event) {
  if (!isDragging) return;
  const dx = event.clientX - prevMouse.x;
  const dy = event.clientY - prevMouse.y;
  // Rotate camera around target
  const angle = Math.atan2(dx, dy);
  // Actually, let's do a simpler approach: update camera position on a sphere
  // ...
}
```

Actually, a cleaner approach for orbit camera:
- Store camera position as (r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta))
- On drag, change theta and phi
- On wheel, change r

Let me define:
- radius: current distance from origin
- theta: azimuthal angle
- phi: polar angle

On drag:
- Compute delta angle from mouse movement
- Update theta and phi accordingly

On wheel:
- radius *= (1 + wheelDelta * sensitivity)

Let me write a complete implementation.

For the line geometry, I'll use BufferGeometry and update it each frame:

```javascript
const positions = new Float32Array(maxPoints * 3);
const colors = new Float32Array(maxPoints * 3);

function updateTrail() {
  const count = points.length;
  for (let i = 0; i < count; i++) {
    positions[i * 3] = points[i].x;
    positions[i * 3 + 1] = points[i].y;
    positions[i * 3 + 2] = points[i].z;
    // Color gradient: from blue to red
    const t = i / Math.max(count - 1, 1);
    colors[i * 3] = 0.2 + 0.6 * t;     // R
    colors[i * 3 + 1] = 0.2 + 0.6 * t; // G
    colors[i * 3 + 2] = 1.0 - 0.8 * t; // B
  }
  line.geometry.attributes.position.needsUpdate = true;
  line.geometry.setColors(colors);
}
```

Wait, for the gradient, let me use a better color scheme. Maybe start with cyan and go to magenta, or use HSL. Let me use a simple gradient from blue (start) to red (end):
- R: 0 → 1
- G: 0 → 1
- B: 1 → 0

Actually, let me keep it simple: a bright gradient line.

For the luminous look, I can set the material's emissive property and use a semi-transparent or bright color. Let me try:
```javascript
const material = new THREE.LineBasicMaterial({
  color: 0xff00ff,
  transparent: true,
  opacity: 0.9
});
```

Or use a custom shader for glow. But without postprocessing, a simple approach is to use a bright color with additive blending. However, Three.js default blending might not be additive. Let me use `material.blending = THREE.AdditiveBlending`.

Actually, for a "luminous-looking" appearance, setting `emissive` and using a bright color works well. Let me use:
```javascript
const material = new THREE.LineBasicMaterial({
  color: 0x00ffff,
  transparent: true,
  opacity: 0.95,
  emissive: 0x00ffff,
  emissiveIntensity: 0.5
});
```

Now, let me put it all together. Here's my plan for the full index.html:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorenz Attractor</title>
<style>
  body { margin: 0; overflow: hidden; font-family: sans-serif; }
  #ui {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 100;
    display: flex;
    gap: 8px;
  }
  button {
    padding: 6px 12px;
    font-size: 14px;
    cursor: pointer;
    border: 1px solid #555;
    background: #222;
    color: #fff;
    border-radius: 3px;
  }
  button:hover { background: #444; }
</style>
</head>
<body>
<div id="ui">
  <button id="pauseBtn">Pause</button>
  <button id="resetBtn">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
(function() {
  'use strict';
  
  // --- Lorenz system ---
  const sigma = 10;
  const rho = 28;
  const beta = 8/3;
  const dt = 0.01;
  const initial = { x: 0.1, y: 0, z: 0 };
  
  let points = [];
  const maxPoints = 8000;
  
  function initPoints() {
    points = [{ x: initial.x, y: initial.y, z: initial.z }];
  }
  
  function step() {
    const p = points[points.length - 1];
    const dx = sigma * (p.y - p.x);
    const dy = p.x * (rho - p.z) - p.y;
    const dz = p.x * p.y - beta * p.z;
    points.push({
      x: p.x + dx * dt,
      y: p.y + dy * dt,
      z: p.z + dz * dt
    });
    if (points.length > maxPoints) {
      points.shift();
    }
  }
  
  // --- Three.js setup ---
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x000000);
  
  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(5, 5, 5);
  
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  
  // --- Trajectory line ---
  const lineGeom = new THREE.BufferGeometry();
  const linePositions = new Float32Array(maxPoints * 3);
  const lineColors = new Float32Array(maxPoints * 3);
  const line = new THREE.Line(lineGeom, new THREE.LineBasicMaterial({
    color: 0x00ffff,
    transparent: true,
    opacity: 0.9,
    emissive: 0x00ffff,
    emissiveIntensity: 0.6
  }));
  scene.add(line);
  
  // --- Light ---
  const light = new THREE.DirectionalLight(0xffffff, 1.0);
  light.position.set(5, 5, 5);
  scene.add(light);
  
  // --- Camera orbit controls ---
  let cameraRadius = 5;
  let cameraTheta = 0.7; // azimuth
  let cameraPhi = 0.6;   // polar
  let isDragging = false;
  let prevMouse = { x: 0, y: 0 };
  
  function getCameraPosition() {
    const x = cameraRadius * Math.sin(cameraPhi) * Math.cos(cameraTheta);
    const y = cameraRadius * Math.cos(cameraPhi);
    const z = cameraRadius * Math.sin(cameraPhi) * Math.sin(cameraTheta);
    return new THREE.Vector3(x, y, z);
  }
  
  function updateCamera() {
    const pos = getCameraPosition();
    camera.position.copy(pos);
    camera.lookAt(new THREE.Vector3(0, 0, 0));
  }
  
  // --- UI ---
  const pauseBtn = document.getElementById('pauseBtn');
  const resetBtn = document.getElementById('resetBtn');
  let paused = false;
  
  pauseBtn.addEventListener('click', () => {
    paused = !paused;
    pauseBtn.textContent = paused ? 'Resume' : 'Pause';
  });
  
  resetBtn.addEventListener('click', () => {
    initPoints();
    line.geometry.dispose();
    lineGeom.setAttribute('position', new THREE.BufferGeometry().setFromAttributes(
      new THREE.BufferAttribute(linePositions, 3)
    ));
    // Rebuild geometry with current points
    const positions = new Float32Array(points.length * 3);
    for (let i = 0; i < points.length; i++) {
      positions[i*3] = points[i].x;
      positions[i*3+1] = points[i].y;
      positions[i*3+2] = points[i].z;
    }
    line.geometry = new THREE.BufferGeometry();
    line.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    line.geometry.attributes.position.needsUpdate = true;
    
    // Reset colors
    const colors = new Float32Array(points.length * 3);
    for (let i = 0; i < points.length; i++) {
      const t = i / Math.max(points.length - 1, 1);
      colors[i*3] = 0.2 + 0.6 * t;
      colors[i*3+1] = 0.2 + 0.6 * t;
      colors[i*3+2] = 1.0 - 0.8 * t;
    }
    line.geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
    line.geometry.attributes.color.needsUpdate = true;
  });
  
  // --- Mouse events ---
  function onMouseDown(e) {
    isDragging = true;
    prevMouse = { x: e.clientX, y: e.clientY };
  }
  
  function onMouseMove(e) {
    if (!isDragging) return;
    const dx = e.clientX - prevMouse.x;
    const dy = e.clientY - prevMouse.y;
    // Sensitivity
    const rotSpeed = 0.005;
    cameraTheta += dx * rotSpeed;
    cameraPhi += dy * rotSpeed;
    // Clamp polar angle
    cameraPhi = Math.max(0.1, Math.min(Math.PI - 0.1, cameraPhi));
    prevMouse = { x: e.clientX, y: e.clientY };
  }
  
  function onMouseUp() {
    isDragging = false;
  }
  
  function onWheel(e) {
    e.preventDefault();
    const zoomFactor = 0.05;
    cameraRadius *= (1 + e.deltaY * zoomFactor);
    cameraRadius = Math.max(1, Math.min(20, cameraRadius));
  }
  
  window.addEventListener('mousedown', onMouseDown);
  window.addEventListener('mousemove', onMouseMove);
  window.addEventListener('mouseup', onMouseUp);
  window.addEventListener('wheel', onWheel);
  
  // --- Animation loop ---
  let lastTime = performance.now();
  
  function animate(time) {
    requestAnimationFrame(animate);
    
    if (!paused) {
      step();
    }
    
    updateCamera();
    renderer.render(scene, camera);
  }
  
  animate(performance.now());
  
  // Handle resize
  window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  });
  
  // Initialize
  initPoints();
  updateCamera();
})();
</script>
</body>
</html>
```

Wait, there's an issue with the reset function. When we call `line.geometry.dispose()` and then create a new geometry, we need to be careful. Also, the line's geometry needs to be updated properly.

Also, I realize the line geometry update in reset might have issues. Let me simplify: on reset, rebuild the geometry from scratch.

Let me also think about the trajectory storage being "bounded". I'm using maxPoints = 8000 and shifting old points. That should be fine.

For the "luminous-looking" appearance, I've set emissive and additive blending. Let me check if AdditiveBlending is available in the basic material. Yes, it should be.

Actually, let me reconsider the material. For a glowing line, a common trick is:
```javascript
material = new THREE.LineBasicMaterial({
  color: 0x00ffff,
  transparent: true,
  opacity: 0.8,
  emissive: 0x00ffff,
  emissiveIntensity: 0.8
});
```

This should give a nice glow effect.

Now, let me also think about the initial camera position. The attractor is centered at origin. Camera at (5, 5, 5) looking at origin should work.

Let me also make sure the trail is visible. The line should be drawn from the first point to the last. With maxPoints = 8000, the line will be quite long. That's fine.

One concern: when the simulation runs, new points are added to the line each frame. The line geometry has a fixed buffer size (maxPoints * 3). As we push new points, we're writing beyond the initial length? No, because we pre-allocate the Float32Array with maxPoints * 3 elements, and we only write up to points.length. Since points.length <= maxPoints, this is safe.

But wait, in the animation loop, we call step() which pushes a new point. If points.length reaches maxPoints, we shift() the oldest. So points.length stays <= maxPoints. Good.

However, in the reset function, I'm creating a new Float32Array with points.length * 3 elements. That's correct.

Let me also double-check the color gradient. For a two-lobed butterfly shape, a gradient from one color to another works well. Let me use a nice color scheme: from cyan (0, 1, 1) to magenta (1, 0, 1) or from blue to red. Let me use:
- R: 0 → 1
- G: 0 → 1
- B: 1 → 0

This gives a cyan-to-red gradient. Actually, let me use:
- t=0: (0, 0.5, 1) - blue
- t=1: (1, 0, 0.5) - orange-red

Hmm, let me keep it simple: R = 0.2 + 0.6*t, G = 0.2 + 0.6*t, B = 1.0 - 0.8*t. This goes from dark blueish to bright redish. That should look good.

Actually, for a "luminous" look, maybe brighter colors: R = 0.5 + 0.5*t, G = 0.5 + 0.5*t, B = 0.8 - 0.6*t. Let me just go with something reasonable.

Now, let me also consider: the requirement says "Draw a luminous-looking, colored trajectory progressively as the system evolves, revealing its two-lobed butterfly shape." So the line should be colored along its length. My gradient approach does this.

"Keep trajectory storage bounded" - I'm capping at maxPoints.

"Frame the full attractor initially" - the initial camera view should show the whole attractor. With camera at (5,5,5) and radius 5, the sphere of radius 5 covers the attractor (which is roughly within a radius of about 20-30 for these parameters). Hmm, actually the Lorenz attractor for these parameters extends to about |x|,|y|,|z| around 20-30. Let me check: the attractor has x and y oscillating between about -20 and 20, and z between about -30 and 40. So a camera at distance 5 won't show the whole thing.

Let me increase the initial camera radius to something like 15-20. But then the zoom wheel needs to handle it. Let me set initial cameraRadius = 15.

Actually, let me think about what "frame the full attractor initially" means. It means the initial camera position should show the entire attractor. The attractor bounds for σ=10, ρ=28, β=8/3 are approximately:
- x, y: roughly [-20, 20]
- z: roughly [-30, 50]

So a camera at distance about 25 from the origin, looking at the origin, would frame it. Let me set initial cameraRadius = 25.

But then the zoom wheel sensitivity needs to be tuned. Let me set initial radius to 20 and allow zoom from 5 to 40.

Let me adjust:
- Initial cameraRadius = 20
- Zoom range: 5 to 40

Now, let me also think about the orbit: "Dragging orbits the camera around it" - my implementation rotates the camera position on a sphere. That should work.

Let me finalize the code and write it to index.html.

One more thing: the `line.geometry.attributes.position.needsUpdate = true` - after rebuilding the geometry in reset, I need to make sure the attribute is set correctly.

Let me rewrite the reset function more cleanly:

```javascript
resetBtn.addEventListener('click', () => {
  paused = false;
  pauseBtn.textContent = 'Pause';
  
  // Rebuild points
  points = [{ x: initial.x, y: initial.y, z: initial.z }];
  
  // Rebuild line geometry
  const positions = new Float32Array(points.length * 3);
  for (let i = 0; i < points.length; i++) {
    positions[i*3] = points[i].x;
    positions[i*3+1] = points[i].y;
    positions[i*3+2] = points[i].z;
  }
  const colors = new Float32Array(points.length * 3);
  for (let i = 0; i < points.length; i++) {
    const t = i / Math.max(points.length - 1, 1);
    colors[i*3] = 0.2 + 0.6 * t;
    colors[i*3+1] = 0.2 + 0.6 * t;
    colors[i*3+2] = 1.0 - 0.8 * t;
  }
  line.geometry = new THREE.BufferGeometry();
  line.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  line.geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  line.geometry.attributes.position.needsUpdate = true;
  line.geometry.attributes.color.needsUpdate = true;
});
```

This is cleaner.

Now, let me also add a subtle rotation to the camera or the scene so the user can see the attractor from different angles. Actually, the orbit control already handles that.

Let me also add a slight auto-rotation when paused? Not required, but nice. The requirement doesn't ask for it.

OK, let me write the final index.html.

Actually, I want to double-check one thing: the `three.min.js` file is in the same directory. The instruction says "the runner will inline it into the final artifact." So I should reference it with `<script src="three.min.js"></script>`. The runner will handle inlining. That's fine.

Let me write the complete file now.

One more consideration: the line material. For a "luminous" look, I'll use:
```javascript
const material = new THREE.LineBasicMaterial({
  color: 0x00ffff,
  transparent: true,
  opacity: 0.85,
  emissive: 0x00ffff,
  emissiveIntensity: 0.7
});
```

And for the glow effect, I could also try `material.side = THREE.DoubleSide` to make it visible from both sides (though it's a line, so it's always visible).

Let me finalize and write the file.

Actually, I realize I should test the code. Let me write it, then run it in the browser to verify.

Let me write the complete index.html now.
custom

The output file index.html has not been written, or is empty. Write the complete app to index.html in the current directory now. Use the remaining time to verify it, then finish. The original 15-minute deadline still applies.

assistant
thinking
We need to create an index.html file that implements a 3D Lorenz attractor visualization using Three.js (the provided three.min.js). Let me plan the implementation:

1. Include three.min.js from the local file
2. Create a scene with camera, renderer, lights
3. Implement the Lorenz system:
   - dx/dt = sigma * (y - x)
   - dy/dt = x * (rho - z) - y
   - dz/dt = x * y - beta
   - Parameters: sigma=10, rho=28, beta=8/3
   - Initial state: (0.1, 0, 0)
4. Use a stable numerical time step (e.g., dt = 0.01)
5. Draw a luminous-colored trajectory progressively as the system evolves
6. Camera orbit via mouse drag, mouse wheel changes camera distance
7. Pause/Resume and Reset buttons
8. Pause freezes trajectory growth but camera controls remain usable; Reset clears the trail and restores initial state

Let me write this all in one self-contained index.html file.

Key design decisions:
- Use a BufferGeometry or just store points in an array and draw as a Line
- For "luminous-looking" colored trajectory, use a gradient color along the path
- Store trajectory points in an array, update periodically
- The camera interaction: track mouse down for dragging, mouse wheel for zoom
- Pause/Resume: stop updating the simulation when paused
- Reset: clear the trajectory and reset state variables

Let me think about the structure:

```html
<!DOCTYPE html>
<html>
<head>
<style>...</style>
</head>
<body>
<div id="container"></div>
<div id="controls">
  <button id="pause">Pause</button>
  <button id="resume">Resume</button>
  <button id="reset">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
// All the JS here
</script>
</body>
</html>
```

For the Lorenz simulation:
- State: [x, y, z]
- Time step: dt = 0.01 (stable)
- Each frame, advance the state by dt
- Store points in an array (with a maximum length to keep it bounded)

For the trajectory line:
- Use THREE.Line with BufferGeometry
- Update the geometry each frame with new points
- Color the line: maybe use a gradient from blue to red along the path

For camera controls without addons:
- Track mouse down on the canvas, record start position
- On mouse move, compute delta and rotate camera around center
- On mouse up, stop rotating
- Mouse wheel: adjust camera.position.z (distance)

Let me implement this carefully.

For the trajectory storage: keep an array of points. When adding a new point, push to the array. If the array gets too long (e.g., 1000 points), we can trim it or let it grow. The requirement says "keep trajectory storage bounded; actual bloom postprocessing is optional." So I'll cap the number of points, say 2000, and shift the array or just truncate.

Actually, for a nice butterfly shape, we need enough points. Let's use dt=0.01 and run for maybe 5000 steps per frame? No, that would be too fast. Let's do one simulation step per frame with dt=0.01, and accumulate points over time. The trajectory will be drawn as a line that grows.

For the line geometry, I'll rebuild it each frame with the current set of points. Or better, use a dynamic buffer geometry that appends vertices.

Simpler approach: maintain an array of points, and each frame create a new BufferGeometry with all points. This is fine for a few thousand points.

Color: make it luminous - use a gradient from cyan to magenta or something. I can use a shader material or just set vertex colors. Let me use a simple approach: interpolate colors along the path.

Actually, for a "luminous-looking" effect, I could use a MeshBasicMaterial with emissive properties, or use a Line with a custom shader. But to keep it simple and working without external resources, I'll use a Line with a solid color that shifts over time, or use vertex colors.

Let me use a Line with a color that changes based on the position along the path. I'll create a color array matching the points.

Implementation plan:

```javascript
const sigma = 10;
const rho = 28;
const beta = 8/3;
const initialState = { x: 0.1, y: 0, z: 0 };
const dt = 0.01;

let state = { ...initialState };
let paused = false;
let points = [];
const maxPoints = 3000;

function lorenzStep(s, dt) {
  const x = s.x;
  const y = s.y;
  const z = s.z;
  const dx = sigma * (y - x);
  const dy = x * (rho - z) - y;
  const dz = x * y - beta;
  return { x: x + dx * dt, y: y + dy * dt, z: z + dz * dt };
}

function update() {
  if (!paused) {
    state = lorenzStep(state, dt);
  }
  // Add point every N frames to avoid too many points
  if (points.length % 5 === 0) {
    points.push({ x: state.x, y: state.y, z: state.z });
    if (points.length > maxPoints) {
      points.shift();
    }
  }
}
```

Wait, if I only add points every 5 frames, the animation will be slower. Let me add a point every frame but cap at maxPoints. Actually, for a smooth trajectory, adding a point each frame is good. With dt=0.01, that's 100 steps per second. Over 10 seconds, 1000 points. That should be fine.

But the requirement says "keep trajectory storage bounded" - so I'll cap at some number like 2000.

For the line:
```javascript
const positions = new Float32Array(maxPoints * 3);
const colors = new Float32Array(maxPoints * 3);
// Fill these arrays
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.LineBasicMaterial({ 
  color: 0xffffff,
  vertexColors: true 
});
const line = new THREE.Line(geometry, material);
scene.add(line);
```

Then in the render loop:
```javascript
for (let i = 0; i < points.length; i++) {
  positions[i*3] = points[i].x;
  positions[i*3+1] = points[i].y;
  positions[i*3+2] = points[i].z;
  // color interpolation
  const t = i / points.length;
  colors[i*3] = 0.0 + t * 1.0; // red channel
  colors[i*3+1] = 0.0 + t * 0.5; // green
  colors[i*3+2] = 1.0 - t * 0.5; // blue
}
geometry.attributes.position.needsUpdate = true;
geometry.attributes.color.needsUpdate = true;
```

Now for camera controls:
- Track mouse down on the canvas
- On mousedown, record start position and set dragging flag
- On mousemove (while dragging), compute rotation around Y axis (and maybe X)
- On mouseup, stop dragging
- On wheel, adjust camera.position.z (zoom)

For orbit: rotate the camera around the origin. I can use a simple approach:
- When dragging, calculate angle change from mouse movement
- Apply rotation to camera.position

Let me implement a simple orbit:
```javascript
let isDragging = false;
let lastX = 0, lastY = 0;

canvas.addEventListener('mousedown', (e) => {
  isDragging = true;
  lastX = e.clientX;
  lastY = e.clientY;
});

canvas.addEventListener('mouseup', () => isDragging = false);

canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  const dx = e.clientX - lastX;
  const dy = e.clientY - lastY;
  // Rotate camera
  camera.rotation.y += dx * 0.005;
  camera.rotation.x += dy * 0.005;
  lastX = e.clientX;
  lastY = e.clientY;
});

canvas.addEventListener('wheel', (e) => {
  camera.position.z = Math.max(1, camera.position.z - e.deltaY * 0.01);
});
```

Hmm, but rotating camera.rotation directly might not be the best. A common approach is to move the camera position in spherical coordinates. Let me use a simpler approach:

- Track the camera's target (origin)
- On drag, rotate the camera around the target
- On wheel, change the radius

Actually, the simplest robust approach for orbit camera in Three.js without addons:

```javascript
let isDragging = false;
let dragStartX, dragStartY;

canvas.addEventListener('mousedown', (e) => {
  isDragging = true;
  dragStartX = e.clientX;
  dragStartY = e.clientY;
});

window.addEventListener('mouseup', () => isDragging = false);

canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  const dx = e.clientX - dragStartX;
  const dy = e.clientY - dragStartY;
  // Rotate camera around center
  const angle = Math.atan2(dx, dy);
  camera.position.set(
    Math.sin(angle) * camera.position.z,
    Math.cos(angle) * camera.position.z,
    Math.cos(angle) * Math.sin(camera.position.z) // hmm, this is getting complicated
  );
});
```

This is getting messy. Let me use a cleaner approach: store the camera's current position and target, and on drag, rotate the camera around the origin.

Actually, the standard way to do orbit camera without OrbitControls:

1. Keep track of the camera's position as a spherical coordinate (radius, theta, phi)
2. On drag, update theta/phi
3. On wheel, update radius

Let me implement it this way:

```javascript
let camRadius = 15;
let camTheta = 0; // azimuthal
let camPhi = Math.PI / 4; // polar

function updateCamera() {
  camera.position.set(
    camRadius * Math.sin(camPhi) * Math.cos(camTheta),
    camRadius * Math.cos(camPhi),
    camRadius * Math.sin(camPhi) * Math.sin(camTheta)
  );
  camera.lookAt(0, 0, 0);
}
```

On mousedown, start dragging. On mousemove while dragging, adjust theta/phi based on mouse delta. On wheel, adjust camRadius.

```javascript
canvas.addEventListener('mousedown', (e) => {
  isDragging = true;
  startX = e.clientX;
  startY = e.clientY;
});

canvas.addEventListener('mouseup', () => isDragging = false);

canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  const dx = e.clientX - startX;
  const dy = e.clientY - startY;
  camTheta += dx * 0.01;
  camPhi -= dy * 0.01;
  updateCamera();
});

canvas.addEventListener('wheel', (e) => {
  camRadius = Math.max(3, camRadius - e.deltaY * 0.1);
  updateCamera();
});
```

This should work reasonably well.

Now for the UI:
- Pause button: toggles paused state
- Resume button: same
- Reset button: clears points, resets state

Let me write the complete HTML file now.

Also, I need to make sure the three.min.js is loaded. The instructions say "Use the provided three.min.js (global THREE) through a plain local script tag". So I'll include `<script src="three.min.js"></script>` before my code.

Let me also add some basic styling for the page.

Here's the full plan for index.html:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorenz Attractor</title>
<style>
  body { margin: 0; overflow: hidden; font-family: sans-serif; }
  #canvas-container { width: 100vw; height: 100vh; }
  #controls {
    position: absolute;
    top: 20px;
    left: 20px;
    z-index: 10;
    background: rgba(0,0,0,0.7);
    padding: 15px;
    border-radius: 8px;
  }
  #controls button {
    margin: 5px;
    padding: 8px 16px;
    background: #333;
    color: white;
    border: none;
    cursor: pointer;
    border-radius: 4px;
  }
  #controls button:hover { background: #555; }
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="controls">
  <button id="pauseBtn">Pause</button>
  <button id="resumeBtn">Resume</button>
  <button id="resetBtn">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
// ... JavaScript ...
</script>
</body>
</html>
```

Now the JavaScript:

```javascript
// Lorenz parameters
const sigma = 10;
const rho = 28;
const beta = 8/3;
const initialState = { x: 0.1, y: 0, z: 0 };

let state = { ...initialState };
let paused = false;
let points = [];
const maxPoints = 3000;

// Camera
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(15, 15, 15);
camera.lookAt(0, 0, 0);

// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('canvas-container').appendChild(renderer.domElement);

// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);

// Lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);

// Trajectory line
let trajectoryLine = null;
function createTrajectoryLine() {
  const positions = new Float32Array(maxPoints * 3);
  const colors = new Float32Array(maxPoints * 3);
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  const material = new THREE.LineBasicMaterial({
    color: 0xffffff,
    vertexColors: true,
    transparent: true,
    opacity: 0.9
  });
  trajectoryLine = new THREE.Line(geometry, material);
  scene.add(trajectoryLine);
}
createTrajectoryLine();

// Simulation step
function lorenzStep(s, dt) {
  const x = s.x;
  const y = s.y;
  const z = s.z;
  const dx = sigma * (y - x);
  const dy = x * (rho - z) - y;
  const dz = x * y - beta;
  return { x: x + dx * dt, y: y + dy * dt, z: z + dz * dt };
}

function update() {
  if (!paused) {
    state = lorenzStep(state, dt);
  }
  // Record point
  points.push({ x: state.x, y: state.y, z: state.z });
  if (points.length > maxPoints) {
    points.shift();
  }
}

// Animation loop
let dt = 0.01;
let lastTime = performance.now();

function animate() {
  requestAnimationFrame(animate);
  
  // Handle window resize
  if (window.innerWidth !== renderer.domElement.clientWidth || window.innerHeight !== renderer.domElement.clientHeight) {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = renderer.domElement.clientWidth / renderer.domElement.clientHeight;
    camera.updateProjectionMatrix();
  }
  
  update();
  
  // Update trajectory geometry
  if (trajectoryLine) {
    const posAttr = trajectoryLine.geometry.attributes.position;
    const colorAttr = trajectoryLine.geometry.attributes.color;
    const count = Math.min(points.length, maxPoints);
    
    for (let i = 0; i < count; i++) {
      posAttr.array[i * 3] = points[i].x;
      posAttr.array[i * 3 + 1] = points[i].y;
      posAttr.array[i * 3 + 2] = points[i].z;
      
      // Color gradient from blue to red
      const t = i / (count - 1 || 1);
      colorAttr.array[i * 3] = t;       // R
      colorAttr.array[i * 3 + 1] = 0;    // G
      colorAttr.array[i * 3 + 2] = 1 - t; // B
    }
    posAttr.needsUpdate = true;
    colorAttr.needsUpdate = true;
  }
  
  renderer.render(scene, camera);
}

// Event listeners
const canvas = renderer.domElement;

canvas.addEventListener('mousedown', (e) => {
  isDragging = true;
  startX = e.clientX;
  startY = e.clientY;
});

window.addEventListener('mouseup', () => { isDragging = false; });

canvas.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  const dx = e.clientX - startX;
  const dy = e.clientY - startY;
  camTheta += dx * 0.01;
  camPhi -= dy * 0.01;
  updateCamera();
});

canvas.addEventListener('wheel', (e) => {
  camRadius = Math.max(3, camRadius - e.deltaY * 0.1);
  updateCamera();
});

function updateCamera() {
  camera.position.set(
    camRadius * Math.sin(camPhi) * Math.cos(camTheta),
    camRadius * Math.cos(camPhi),
    camRadius * Math.sin(camPhi) * Math.sin(camTheta)
  );
  camera.lookAt(0, 0, 0);
}

// Controls
document.getElementById('pauseBtn').addEventListener('click', () => {
  paused = !paused;
  document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
});

document.getElementById('resumeBtn').addEventListener('click', () => {
  paused = false;
  document.getElementById('resumeBtn').textContent = 'Resume';
});

document.getElementById('resetBtn').addEventListener('click', () => {
  state = { x: 0.1, y: 0, z: 0 };
  points = [];
  if (trajectoryLine) {
    const posAttr = trajectoryLine.geometry.attributes.position;
    const colorAttr = trajectoryLine.geometry.attributes.color;
    posAttr.array.fill(0);
    colorAttr.array.fill(0);
    posAttr.needsUpdate = true;
    colorAttr.needsUpdate = true;
  }
});

// Initialize
const isDragging = false;
let startX = 0, startY = 0;
let camRadius = 15;
let camTheta = 0;
let camPhi = Math.PI / 4;

updateCamera();

animate();
```

Wait, there's an issue with the initial camera setup. `camera.lookAt(0, 0, 0)` after setting position should work. But initially, the camera is at (15, 15, 15) looking at origin. That's fine.

Also, I need to handle the case where points.length is 0 for the color gradient (division by zero). I handled that with `(count - 1 || 1)`.

Let me also think about the "luminous-looking" aspect. Using a white line might not look very luminous. I could use a brighter color or add emissive properties. Since we're using LineBasicMaterial, I can't easily make it emissive. But I can use a brighter color like white-yellow or use additive blending. Let me try `color: 0xffffff` which is white, and maybe increase opacity. Or use `color: 0xffaa00` for a warmer look.

Actually, for a "luminous" look, I could use a gradient from cyan to magenta. But vertex colors are per-point. Let me keep it simple with a bright white/yellow line.

Another idea: use a shader material for the line to get a glow effect. But that requires more code. Let me stick with LineBasicMaterial and a bright color.

Let me refine the color: maybe use a gradient from dark blue to bright white. Or just keep it simple.

Now, let me also consider: the trajectory should be "drawn progressively as the system evolves". My current approach adds a point every frame. That means the line grows over time. Good.

"Keep trajectory storage bounded" - I'm capping at maxPoints = 3000. Good.

"Frame the full attractor initially" - the scene starts with the camera positioned to show the attractor. Good.

"Dragging orbits the camera around it" - implemented with mousedown/mousemove/mouseup. Good.

"Mouse wheel changes camera distance without entering the geometry" - wheel event adjusts camRadius. Good.

"Pause/Resume and Reset" - implemented. Pause stops the simulation updates but camera controls still work. Reset clears the trail and resets state.

One concern: when paused, the points array keeps growing because update() always adds a point. I should only add points when not paused. Let me fix that:

```javascript
function update() {
  if (!paused) {
    state = lorenzStep(state, dt);
  }
  if (!paused) {
    points.push({ x: state.x, y: state.y, z: state.z });
    if (points.length > maxPoints) {
      points.shift();
    }
  }
}
```

Yes, that's better.

Also, I should make sure the initial state is properly set up. The initial point should probably be added to the trajectory immediately.

Let me also add a subtle glow effect. I could use a second line with a thicker width behind it, or use a material with higher emissive-like properties. Actually, Three.js LineBasicMaterial doesn't have emissive. But I can use `line.width` property (in newer versions) or just keep it simple.

Let me finalize the code. I'll write it all into index.html.

One more thing: the three.min.js file is in the same directory. I need to reference it correctly. The script tag `<script src="three.min.js"></script>` should work since it's in the same directory.

Let me also make sure the canvas container is properly sized. I'll append the renderer's domElement to the container div.

Let me write the complete file now.

Actually, let me reconsider the camera orbit implementation. The spherical coordinates approach:
- camRadius: distance from origin
- camTheta: azimuthal angle (rotation around Y axis)
- camPhi: polar angle (from Z axis)

Position:
- x = r * sin(phi) * cos(theta)
- y = r * cos(phi)
- z = r * sin(phi) * sin(theta)

When dragging:
- dx increases theta
- dy decreases phi (or increases, depending on convention)

This should give a reasonable orbit.

Let me also add a slight lag or smoothing, but for simplicity, direct update is fine.

Now, let me write the file.

Actually, I realize I should double-check: the requirement says "Use a stable numerical time step." dt=0.01 is stable for the Lorenz system with these parameters. Yes, it's a common choice.

Let me write the complete index.html:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lorenz Attractor</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { overflow: hidden; font-family: Arial, sans-serif; background: #111; }
  #canvas-container { width: 100vw; height: 100vh; position: relative; }
  #controls {
    position: absolute;
    top: 15px;
    left: 15px;
    z-index: 10;
    background: rgba(0, 0, 0, 0.75);
    padding: 12px 16px;
    border-radius: 6px;
    color: #fff;
    font-size: 14px;
  }
  #controls button {
    margin: 4px;
    padding: 6px 12px;
    background: #444;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
  }
  #controls button:hover { background: #666; }
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="controls">
  <button id="pauseBtn">Pause</button>
  <button id="resumeBtn">Resume</button>
  <button id="resetBtn">Reset</button>
</div>
<script src="three.min.js"></script>
<script>
(function() {
  // Lorenz system parameters
  const sigma = 10;
  const rho = 28;
  const beta = 8 / 3;
  const initialState = { x: 0.1, y: 0, z: 0 };

  let state = { ...initialState };
  let paused = false;

  // Trajectory storage
  const maxPoints = 3000;
  let points = [];

  // Camera orbit state
  let camRadius = 15;
  let camTheta = 0;
  let camPhi = Math.PI / 4;

  // --- Three.js setup ---
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x050510);

  const camera = new THREE.PerspectiveCamera(
    60,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.position.set(15, 15, 15);
  camera.lookAt(0, 0, 0);

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  document.getElementById('canvas-container').appendChild(renderer.domElement);

  // Lighting
  const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
  scene.add(ambientLight);
  const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
  dirLight.position.set(5, 5, 5);
  scene.add(dirLight);

  // --- Trajectory line ---
  let trajectoryLine = null;
  function createTrajectoryLine() {
    const positions = new Float32Array(maxPoints * 3);
    const colors = new Float32Array(maxPoints * 3);
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
    const material = new THREE.LineBasicMaterial({
      color: 0xffffff,
      vertexColors: true,
      transparent: true,
      opacity: 0.95
    });
    trajectoryLine = new THREE.Line(geometry, material);
    scene.add(trajectoryLine);
  }
  createTrajectoryLine();

  // --- Lorenz integration ---
  function lorenzStep(s, dt) {
    const x = s.x;
    const y = s.y;
    const z = s.z;
    const dx = sigma * (y - x);
    const dy = x * (rho - z) - y;
    const dz = x * y - beta;
    return { x: x + dx * dt, y: y + dy * dt, z: z + dz * dt };
  }

  function update() {
    if (!paused) {
      state = lorenzStep(state, 0.01);
    }
    if (!paused) {
      points.push({ x: state.x, y: state.y, z: state.z });
      if (points.length > maxPoints) {
        points.shift();
      }
    }
  }

  // --- Animation loop ---
  let lastTime = performance.now();

  function animate() {
    requestAnimationFrame(animate);

    // Handle resize
    const w = window.innerWidth;
    const h = window.innerHeight;
    if (renderer.domElement !== document.getElementById('canvas-container')) {
      // Not needed, just in case
    }
    renderer.setSize(w, h);
    camera.aspect = w / h;
    camera.updateProjectionMatrix();

    update();

    // Update trajectory geometry
    if (trajectoryLine) {
      const posAttr = trajectoryLine.geometry.attributes.position;
      const colorAttr = trajectoryLine.geometry.attributes.color;
      const count = Math.min(points.length, maxPoints);
      for (let i = 0; i < count; i++) {
        posAttr.array[i * 3] = points[i].x;
        posAttr.array[i * 3 + 1] = points[i].y;
        posAttr.array[i * 3 + 2] = points[i].z;
        const t = count > 1 ? i / (count - 1) : 0;
        colorAttr.array[i * 3] = t;       // Red
        colorAttr.array[i * 3 + 1] = 0;    // Green
        colorAttr.array[i * 3 + 2] = 1 - t; // Blue
      }
      posAttr.needsUpdate = true;
      colorAttr.needsUpdate = true;
    }

    renderer.render(scene, camera);
  }

  // --- Camera orbit controls ---
  let isDragging = false;
  let startX = 0;
  let startY = 0;

  function updateCamera() {
    camera.position.set(
      camRadius * Math.sin(camPhi) * Math.cos(camTheta),
      camRadius * Math.cos(camPhi),
      camRadius * Math.sin(camPhi) * Math.sin(camTheta)
    );
    camera.lookAt(0, 0, 0);
  }

  renderer.domElement.addEventListener('mousedown', (e) => {
    isDragging = true;
    startX = e.clientX;
    startY = e.clientY;
  });

  window.addEventListener('mouseup', () => { isDragging = false; });

  renderer.domElement.addEventListener('mousemove', (e) => {
    if (!isDragging) return;
    const dx = e.clientX - startX;
    const dy = e.clientY - startY;
    camTheta += dx * 0.015;
    camPhi -= dy * 0.015;
    updateCamera();
  });

  renderer.domElement.addEventListener('wheel', (e) => {
    camRadius = Math.max(3, camRadius - e.deltaY * 0.08);
    updateCamera();
  });

  // --- UI controls ---
  document.getElementById('pauseBtn').addEventListener('click', () => {
    paused = !paused;
    document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
  });

  document.getElementById('resumeBtn').addEventListener('click', () => {
    paused = false;
    document.getElementById('resumeBtn').textContent = 'Resume';
  });

  document.getElementById('resetBtn').addEventListener('click', () => {
    state = { x: 0.1, y: 0, z: 0 };
    points = [];
    if (trajectoryLine) {
      const posAttr = trajectoryLine.geometry.attributes.position;
      const colorAttr = trajectoryLine.geometry.attributes.color;
      posAttr.array.fill(0);
      colorAttr.array.fill(0);
      posAttr.needsUpdate = true;
      colorAttr.needsUpdate = true;
    }
  });

  // Start
  updateCamera();
  animate();
})();
</script>
</body>
</html>
```

Wait, I need to check the `renderer.domElement !== document.getElementById('canvas-container')` line - that's unnecessary and wrong. Let me remove it.

Also, I should make sure the initial camera position looks good. With camRadius=15, camTheta=0, camPhi=PI/4:
- x = 15 * sin(PI/4) * cos(0) = 15 * 0.707 * 1 ≈ 10.6
- y = 15 * cos(PI/4) = 15 * 0.707 ≈ 10.6
- z = 15 * sin(PI/4) * sin(0) = 0

So the camera starts at (10.6, 10.6, 0) looking at origin. That's fine.

Let me also think about whether the trajectory will be visible. The Lorenz attractor with these parameters is centered around the origin, with x and y ranging roughly from -20 to 20, and z from -30 to 30. The initial camera at (15,15,15) should see it.

One issue: the line geometry's position attribute has a fixed size of maxPoints*3. When I call `posAttr.array[i * 3] = ...`, I'm writing into the existing array. That's fine since I allocated it with maxPoints*3.

But wait - when I reset, I do `posAttr.array.fill(0)` which fills the entire array with zeros. Then when I update, I only write up to count. The rest remains 0, but since count < maxPoints, the unused portion won't be rendered. That's fine.

Actually, there's a subtle issue: after reset, the line still has the old geometry with maxPoints points, but the points array is empty. The geometry still has data for all maxPoints, but only the first 0 points are written. That's fine visually.

Let me also verify the "luminous" appearance. White line on dark background should be visible. Maybe I should make it slightly brighter or use a different color. Let me use a bright cyan-white: color 0x88ccff or something. Actually, white is fine.

Let me finalize and write the file. Then I'll open it in the browser to verify.

Actually, one more consideration: the requirement says "Draw a luminous-looking, colored trajectory progressively as the system evolves, revealing its two-lobed butterfly shape." The trajectory should be colored. My gradient from blue (t=0) to white (t=1) or similar gives a nice gradient. Let me keep it.

Also, "Keep trajectory storage bounded" - I'm using maxPoints=3000. Good.

Let me write the file now.