oneshotlm
Compare all models on this prompt →

Rubik's Cube · lfm-2.5-24b-a2b

No runnable output Cost / run $0.0218 Runtime 139.1s

Prompt for this run

Build an interactive 3D 3 by 3 Rubik's Cube 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: - Start solved, with six distinct face colors and visible cubie boundaries. Preserve cubie and sticker state through moves; recoloring a static cube is not sufficient. - Provide visible buttons for U, D, L, R, F, B quarter turns and their inverses. Animate each move and queue or disable further moves until it completes so state is not corrupted. - Dragging the background orbits the camera without turning a face. Keep the cube fully visible initially and support bounded wheel zoom. - Provide Scramble, applying a sequence of legal turns, and Reset to restore the solved cube and initial camera. - Only core global THREE is provided; implement camera controls without importing unavailable addons. A solver is not required. Verify: Turn one face and apply its inverse, checking that the visible stickers return to their starting arrangement. Orbit the camera to inspect the cube from another angle, then use Reset and check the solved appearance and initial framing.

⚠ 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

runtime139.1s
tokens in15k
tokens out30k
cached2.3k
cost / run$0.0218
Transcript · 9 messages
system

user

Build an interactive 3D 3 by 3 Rubik's Cube 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: - Start solved, with six distinct face colors and visible cubie boundaries. Preserve cubie and sticker state through moves; recoloring a static cube is not sufficient. - Provide visible buttons for U, D, L, R, F, B quarter turns and their inverses. Animate each move and queue or disable further moves until it completes so state is not corrupted. - Dragging the background orbits the camera without turning a face. Keep the cube fully visible initially and support bounded wheel zoom. - Provide Scramble, applying a sequence of legal turns, and Reset to restore the solved cube and initial camera. - Only core global THREE is provided; implement camera controls without importing unavailable addons. A solver is not required. Verify: Turn one face and apply its inverse, checking that the visible stickers return to their starting arrangement. Orbit the camera to inspect the cube from another angle, then use Reset and check the solved appearance and initial framing.

assistant
thinking
We need to build an interactive 3x3 Rubik's Cube using Three.js. The instructions say:

- Use provided `three.min.js` (global `THREE`) through a plain local script tag
- Write to `index.html` in the current working directory
- Inline CSS and JavaScript
- No external assets, CDNs, or network requests
- Build a self-contained index.html

Required behavior:
1. Start solved, with six distinct face colors and visible cubie boundaries
2. Visible buttons for U, D, L, R, F, B quarter turns and their inverses. Animate each move and queue or disable further moves until it completes.
3. Drag the background orbits the camera without turning a face. Keep cube fully visible initially, support bounded wheel zoom.
4. Scramble (sequence of legal turns) and Reset to restore solved cube and initial camera.
5. Only core global THREE is provided; implement camera controls without importing unavailable addons.

Verification: Turn one face and apply its inverse, checking that stickers return to starting arrangement. Orbit camera, then Reset and check solved appearance and initial framing.

Let me think about how to implement this. I need to create a 3x3 Rubik's cube from scratch using Three.js primitives. Each small cube (cubie) has 6 faces, but we can represent the cube as a collection of small cubes.

Actually, let me think about the approach:

**Cube representation:**
- A 3x3x3 cube made of 27 smaller cubes
- But only the outer ones are visible (the surface)
- Each small cube has colored stickers on its faces

For a standard Rubik's cube:
- Center pieces: fixed relative to each other (but can rotate)
- Edge pieces: 12 total
- Corner pieces: 8 total

But for simplicity, I'll model it as a 3x3 grid of cubies where each cubie has up to 3 visible faces.

**Implementation plan:**

1. Create a scene with a camera
2. Build the cube structure:
   - 26 visible cubies (3x3x3 minus the center hidden inside)
   - Each cubie is a small box with colored faces
3. Implement moves:
   - U (up), D (down), L (left), R (right), F (front), B (back) quarter turns
   - Each move rotates a layer of cubies
4. Animation: animate the rotation smoothly
5. Queue/disable moves during animation
6. Drag to orbit camera: use mouse drag to rotate the camera around the cube (orbit controls)
7. Scramble: sequence of random legal moves
8. Reset: return to solved state and initial camera position

Let me think about the Three.js API. Since we're using the provided three.min.js with global THREE, I'll write code like:

```html
<script src="three.min.js"></script>
<script>
// THREE is available globally
</script>
```

Now, let me design the cube data structure.

**Cube geometry:**
- The cube is 3 units per side, centered at origin
- Each small cubie is 1 unit
- Positions: x ∈ {-1, 0, 1}, y ∈ {-1, 0, 1}, z ∈ {-1, 0, 1}
- Total 27 positions, but the center (0,0,0) is not visible
- Actually, for a 3x3x3, all 27 are there, but the inner one is hidden

**Face colors:**
- Front (z=1): Green (or white)
- Back (z=-1): Blue
- Left (x=-1): Orange
- Right (x=1): Red
- Top (y=1): White
- Bottom (y=-1): Yellow

Standard color scheme:
- U (Up/Top): White
- D (Down/Bottom): Yellow
- F (Front): Green
- B (Back): Blue
- L (Left): Orange
- R (Right): Red

**Stickers:**
Each cubie has up to 3 faces that are visible. For example, a corner cubie at (1,1,1) has faces on +x, +y, +z.

I'll create a data structure:
```javascript
const cube = {
  // 3D array: [x][y][z] -> cubie object or null
  // Or a flat list
};
```

Actually, let me simplify. I'll create the cube as a collection of small boxes, each with a material that has colored faces. But since we need to track sticker state, I should store the color of each face of each cubie.

Let me define the cubies by their position indices. For each cubie at position (x, y, z):
- Determine which faces are visible (based on whether x, y, z is at the boundary)
- Assign colors based on the face direction

For the solved state:
- Each cubie's visible faces have specific colors

**Move implementation:**
A quarter turn rotates a layer of cubies. For example, U (Up face clockwise when looking from top):
- Rotates the top layer (y=1) around the y-axis
- Cubies at (x, 1, z) for x,z ∈ {-1,0,1} get rotated

Let me define the coordinate system:
- x: left (-1) to right (1)
- y: bottom (-1) to top (1)
- z: back (-1) to front (1)

Faces:
- +x (right): Red
- -x (left): Orange
- +y (top): White
- -y (bottom): Yellow
- +z (front): Green
- -z (back): Blue

**Data structure:**
I'll create an array of 27 cubies, indexed by (x+1)*9 + (y+1)*3 + (z+1). Each cubie has:
- position: {x, y, z}
- faces: {front, back, left, right, top, bottom} or null if not visible
- color: the base color of the cubie (for static parts)

Wait, actually for a Rubik's cube, the center pieces don't move (they're fixed in orientation relative to the core). But for simplicity, I'll treat all cubies as movable pieces.

Hmm, but the requirement says "preserve cubie and sticker state through moves; recoloring a static cube is not sufficient." This means I need to track the actual state of each sticker, not just rotate the geometry.

So I need to maintain a data structure representing the color of each face of each cubie, and update it on each move.

Let me define:
- Each cubie has a position and a set of face colors
- When a move happens, the cubies rotate, and their face colors move with them

**Cubie positions:** Let me use integer coordinates from -1 to 1.

**Solved state:**
- At each position (x,y,z), the cubie has colors on its visible faces:
  - If x == 1: right face = Red
  - If x == -1: left face = Orange
  - If y == 1: top face = White
  - If y == -1: bottom face = Yellow
  - If z == 1: front face = Green
  - If z == -1: back face = Blue

**Move functions:**
Each move rotates a layer. For example, U (rotate top layer y=1 clockwise):
- The 9 cubies at y=1 rotate: (x, 1, z) → (z, 1, -x) for clockwise... let me think.

Clockwise rotation of the top face (looking from above, i.e., from +y direction):
- (x, 1, z) goes to (z, 1, -x)
- Example: (1,1,1) → (1,1,-1)? Let me verify: looking from top, clockwise means:
  - Right edge goes to front edge: (1,1,1) → (1,1,-1)? No wait.

Let me think carefully. Looking down from +y:
- x goes right, z goes forward (into screen)
- Clockwise rotation: a point at (x, z) in the xz-plane rotates to (z, -x)
- So (1, 1) → (1, -1)? That doesn't seem right.

Standard rotation matrix for 90° clockwise around y-axis:
[x']   [ 0  0  1] [x]
[y'] = [-1  0  0] [y]  (this is for counterclockwise around y)
[z']   [ 0  1  0] [z]

For clockwise (when looking from +y toward origin), the transformation is:
x' = z
z' = -x

So (1, 1, 1) → (1, 1, -1)? No: x'=z=1, z'=-x=-1. So (1,1,1) → (1,1,-1).

Hmm, let me verify with a known point. The top-front-right corner is (1,1,1). After a clockwise U turn, it should go to top-back-right: (1,1,-1). Yes, that's correct! The corner moves from front-right-top to back-right-top.

And top-back-right (1,1,-1) → top-back-left (1,-1,-1)? x'=z=-1? Wait: (1,1,-1) → x'=z=-1, z'=-x=-1. So (-1,1,-1). Hmm, that's top-back-left. Correct!

OK so the mapping for U (clockwise, looking from top):
- (x, 1, z) → (z, 1, -x)

Similarly for other faces. Let me define all moves:

**U** (Up, clockwise looking from top): y=1 layer rotates
- (x, 1, z) → (z, 1, -x)

**D** (Down, clockwise looking from top / counterclockwise from bottom): y=-1 layer
- (x, -1, z) → (-z, -1, x)

**L** (Left, clockwise looking from left side): x=-1 layer
- (-1, y, z) → (z, y, -1)? Let me derive. Looking from left (+x direction), clockwise means:
- y' = z, z' = -y? 
- Standard rotation around x-axis: 90° clockwise (looking from +x) is:
  - y' = z
  - z' = -y
- So (-1, y, z) → (-1, z, -y)

**R** (Right, clockwise looking from right side): x=1 layer
- (1, y, z) → (-z, y, 1)
- Check: (1, y, z) → (-z, y, 1)

**F** (Front, clockwise looking from front): z=1 layer
- (x, y, 1) → (-y, x, 1)
- Check: (1, 1, 1) → (-1, 1, 1)? Hmm, front-right-top should go to front-left-top after F clockwise. Front-left-top is (-1, 1, 1). Yes: x'=-y=-1, y'=x=1. Correct.

**B** (Back, clockwise looking from back): z=-1 layer
- (x, y, -1) → (y, -x, -1)
- Check: front-right-top (1,1,1) is not affected. Back-left-top (1,-1,1)... hmm, let me think. Back face is at z=-1. A point on the back face: (x, y, -1). After B clockwise (looking from back, i.e., from -z direction), the mapping is:
- x' = y, y' = -x
- So (1, 1, -1) → (1, -1, -1)? That would be back-right-bottom? Hmm, this is getting confusing.

Let me use a consistent convention. I'll define each move's rotation matrix and apply it to the coordinates.

For a rotation of a layer, I'll use the following:

**U** (rotate top face clockwise, looking from +y):
- Layer y=1: (x, 1, z) → (z, 1, -x)

**D** (rotate bottom face clockwise, looking from +y, i.e., from above):
- Layer y=-1: (x, -1, z) → (-z, -1, x)

**L** (rotate left face clockwise, looking from -x direction):
- Layer x=-1: (-1, y, z) → (z, y, -1)

**R** (rotate right face clockwise, looking from +x direction):
- Layer x=1: (1, y, z) → (-z, y, 1)

**F** (rotate front face clockwise, looking from +z direction):
- Layer z=1: (x, y, 1) → (-y, x, 1)

**B** (rotate back face clockwise, looking from -z direction):
- Layer z=-1: (x, y, -1) → (y, -x, -1)

These are standard 90-degree rotations. Let me verify F: (1, 1, 1) → (-1, 1, 1). Front-right-top goes to front-left-top. Correct for a clockwise F turn.

And B: (1, 1, -1) → (1, -1, -1)? Using (x, y, -1) → (y, -x, -1): (1, 1, -1) → (1, -1, -1). That's back-right-bottom? Hmm, the back face has y going from bottom to top. (1,1,-1) is back-right-top. After B clockwise (looking from back), it should go to back-left-top: (-1, 1, -1). But my formula gives (1, -1, -1). That's wrong.

Let me reconsider. For B, looking from -z (back), a clockwise rotation means:
- x' = -y
- y' = x
- z' = -1

Check: (1, 1, -1) → (-1, 1, -1). Yes! That's back-left-top. Correct.

So B: (x, y, -1) → (-y, x, -1)

Let me re-verify all:

**U**: (x, 1, z) → (z, 1, -x) ✓
**D**: (x, -1, z) → (-z, -1, x) ✓
**L**: (-1, y, z) → (z, y, -1) ✓
**R**: (1, y, z) → (-z, y, 1) ✓
**F**: (x, y, 1) → (-y, x, 1) ✓
**B**: (x, y, -1) → (-y, x, -1) ✓

Great.

Now, for the visual representation, I need to create the cube geometry. Let me create a function that builds the cube from the solved state, creating meshes for each cubie with appropriate colors.

**Cubie data structure:**
I'll create an array of 27 cubies. Each cubie has:
- position: {x, y, z}
- faces: object with keys 'front', 'back', 'left', 'right', 'top', 'bottom' and values being the color strings

For the solved state, the colors are determined by position:
- If x == 1: right face = 'red'
- If x == -1: left face = 'orange'
- If y == 1: top face = 'white'
- If y == -1: bottom face = 'yellow'
- If z == 1: front face = 'green'
- If z == -1: back face = 'blue'

For non-visible faces, they're null.

When a cubie moves, its faces move with it. So I need to track the face colors per cubie.

**Animation:**
When a move is triggered, I'll animate the rotation of the affected cubies over some duration (e.g., 300ms). During animation, I'll interpolate the positions of the cubies being rotated.

For simplicity, I could also just do an instant swap and rely on the visual update. But the requirement says "animate each move." So I'll do a smooth rotation animation.

**Camera orbit:**
- Use mouse drag to rotate the camera around the cube (orbit)
- Support bounded zoom (wheel zoom)
- Keep the cube fully visible initially

I'll implement custom orbit controls without using the addon. I can track mouse movement and update the camera's position/orientation.

**Scramble:**
- Apply a sequence of legal turns (e.g., 20 random moves)
- Reset: return to solved state and initial camera position

**Buttons:**
- U, D, L, R, F, B (quarter turns)
- Their inverses: U', D', L', R', F', B' (or U^5, etc.)
- Maybe also Q (queue) and A (allow) but not required

Let me now start coding. First, let me check what's in the current directory.

Actually, let me first check the environment.

Let me plan the file structure:

```html
<!DOCTYPE html>
<html>
<head>
<style>
  /* CSS styles */
</style>
</head>
<body>
  <div id="container"></div>
  <div id="controls">
    <!-- buttons -->
  </div>
  <script>
    // Three.js setup
    // Cube state
    // Move functions
    // Animation
    // Camera controls
    // Scramble/Reset
  </script>
</body>
</html>
```

Let me think about the Three.js scene setup:
- Scene, camera (PerspectiveCamera), renderer
- Lighting: ambient light + directional light
- Orbit-like camera control via mouse drag

For the cube, I'll create:
- A group containing all cubie meshes
- Each cubie: a BoxGeometry (size 1) with a MeshStandardMaterial. But since each cubie has multiple colored faces, I need to either:
  a) Use a single material with multiple colors (not possible directly)
  b) Create a custom mesh with multiple geometries
  c) Use a single box with a color and add stickers

Option (c) is simplest: create a box for each cubie with a color matching its primary face color. But that won't show all 6 faces of each cubie.

Actually, for a Rubik's cube visualization, it's common to show the stickers on each face. Let me think...

For a simpler approach that still meets the requirements ("visible cubie boundaries" and "six distinct face colors"):
- Each cubie is a small box
- The box has a color corresponding to its main face color
- We can see the boundaries between cubies

But "six distinct face colors" suggests the six faces of the whole cube should be visible with their colors. And "visible cubie boundaries" means we should see where the cubies meet.

Let me create a more detailed representation:
- Each cubie is a small box (size 1)
- For each cubie, I'll create a material that has colored faces. Since Three.js BoxGeometry only has one color per material, I can either:
  1. Create a custom geometry with 6 faces (a cube with colored faces)
  2. Use a Mesh with a texture (but no external assets)
  3. Use multiple meshes per cubie (one per face)

Option 3: For each cubie, create 6 thin boxes (one per face) positioned at the cubie's location. This is the most explicit way to show stickers.

Actually, let me keep it simpler. I'll create each cubie as a box with a color. The box will be positioned at the cubie's center. The visible faces of the cube will show the colors of the outer cubies.

For the solved state:
- The outer shell of the 3x3x3 cube will show the 6 colors
- Inner cubies are hidden

Let me calculate positions:
- The overall cube spans from -1.5 to 1.5 in each dimension (if each cubie is size 1 and there are 3 per side)
- Actually, if each cubie is 1 unit and they're arranged 3x3x3, the total size is 3 units. Centered at origin, it goes from -1.5 to 1.5.

Hmm, but if I place cubies at integer coordinates (-1, 0, 1), the total span is 3 units, from -1.5 to 1.5 if I offset by 0.5. Let me think.

If cubie centers are at x ∈ {-1, 0, 1}, then the leftmost face is at -1.5 and rightmost at 1.5. That works.

So:
- Position of cubie at (x, y, z) where x, y, z ∈ {-1, 0, 1}:
  - Box geometry: size 1, centered at (x, y, z)
  - The box extends from x-0.5 to x+0.5, etc.

This way, adjacent cubies share faces (boundaries are visible).

For colors:
- At each position, determine which faces are on the exterior and assign colors:
  - x=1: right face red
  - x=-1: left face orange
  - y=1: top face white
  - y=-1: bottom face yellow
  - z=1: front face green
  - z=-1: back face blue

For the visual, I'll create a box for each cubie with a color. But a single box can only have one color. To show multiple colors on different faces of the same cubie, I'd need multiple boxes per cubie.

Alternative: Use a single box per cubie, but color it based on which face is most prominent? No, that loses information.

Let me go with the multi-face approach: for each cubie, create 6 thin rectangular prisms (one per face) positioned at the cubie's surface. This is verbose but accurate.

Actually, a simpler approach that still looks good:
- Each cubie is a box with a color
- The box's color represents the "primary" face color
- The boundaries between boxes are visible

But the requirement says "six distinct face colors and visible cubie boundaries." I think showing the 6 outer colors is the key. Let me create the cube such that:
- The outer shell shows the 6 colors
- Each cubie is a box
- Adjacent cubies have their shared face visible (the boundary)

I can achieve this by creating each cubie as a box with a color. The boxes will be placed at integer coordinates. The outer boxes will have their outer faces exposed.

For the solved look, the outer faces of the 3x3x3 cube will display the 6 colors. The inner cubies (at x=0, y=0, z=0 and the ones on the interior faces) will be hidden or have neutral colors.

Actually, for a 3x3x3 cube, the visible cubies are those on the surface: 26 cubies (all except the center at (0,0,0)). Each surface cubie has 1, 2, or 3 visible faces.

Let me create the cubies with appropriate colors on their visible faces. I'll use a custom approach: for each cubie, I'll create a group of 6 small boxes (one per face) positioned at the cubie's location. Each small box is 0.3 units thick and has a colored face.

Hmm, this is getting complex. Let me simplify:

**Simplified approach:**
- Each cubie is a box (size 1) with a color
- The color of the box is the color of the cubie's primary face (the face pointing outward from the cube center)
- For the solved state, the outer surface shows the 6 colors
- Boundaries between cubies are visible (adjacent boxes touch)

This is simple and meets the requirements. The "sticker state" is preserved because each cubie's color is tracked.

For the move animation, I'll rotate the positions of the cubies being moved.

Let me code this up.

**State representation:**
```javascript
const cubieStates = {}; // key: "x,y,z" -> { faces: {...} }
```

Actually, let me use an array of 27 entries, indexed by (x+1)*9 + (y+1)*3 + (z+1).

Each cubie has:
```javascript
{
  pos: {x, y, z},
  faces: {
    front: color|null,
    back: color|null,
    left: color|null,
    right: color|null,
    top: color|null,
    bottom: color|null
  }
}
```

In the solved state, the colors are set based on position.

**Move implementation:**
When a move happens, I'll:
1. Update the state (rotate the cubies)
2. Animate the visual positions

For animation, I'll use a simple tween: interpolate the positions of the cubies over ~300ms.

Let me write the code step by step.

First, let me check the current directory and see if there's a three.min.js already.

Actually, the task says "Use the provided three.min.js (global THREE) through a plain local script tag." So I need to include three.min.js. But I don't know where it is. Let me check the directory.

Let me start by exploring the environment.
→ bash {"command":"ls -la /tmp/job-0Nupbg"}
toolResult

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

assistant
thinking
The three.min.js is in the current directory. Let me check its size and maybe peek at it to confirm it's valid Three.js.

Now, let me create the index.html file. I'll build a complete interactive 3x3 Rubik's cube.

Let me plan the full implementation:

**HTML structure:**
- A container div for the Three.js canvas
- A controls panel with buttons for U, D, L, R, F, B and their inverses
- Maybe a Scramble and Reset button

**CSS:**
- Styling for the page, buttons, and canvas container

**JavaScript:**
1. Three.js setup (scene, camera, renderer, lights)
2. Cube state representation
3. Build the cube geometry from the state
4. Move functions (U, D, L, R, F, B and inverses)
5. Animation system
6. Camera orbit controls (mouse drag + wheel zoom)
7. Button handlers
8. Scramble and Reset

Let me write this out.

For the cube geometry, I'll create each cubie as a box with a color. The box will be centered at the cubie's position. For the solved state:
- Cubies at x=1: red right face
- Cubies at x=-1: orange left face
- etc.

I'll create an array of 27 cubies. Each cubie has a `faces` object that tracks the color of each face. In the solved state, only the outer faces have colors.

For the visual, I'll create a mesh for each cubie. To show the boundaries, I'll make the boxes slightly smaller than 1 unit so there's a gap between them? No, actually if they're exactly adjacent (touching), the boundaries are visible as edges. Let me make the box size 0.95 so there's a small gap, or just use size 1 and let them touch.

Actually, for a clean look, I'll use size 1 for each cubie, positioned at integer coordinates. Adjacent cubies will share faces (touch at edges). This creates visible boundaries.

Wait, if two boxes of size 1 are placed at x=0.5 and x=-0.5, they'd overlap. If placed at x=-1 and x=0, they touch at x=0. So positions should be at -1, 0, 1, and the box extends from -1.5 to -0.5 for the leftmost, etc. They touch at the boundaries.

Let me use box geometry with size 1, positioned at the cubie center. The box goes from x-0.5 to x+0.5. So:
- Cubie at x=-1: box from -1.5 to -0.5
- Cubie at x=0: box from -0.5 to 0.5
- Cubie at x=1: box from 0.5 to 1.5

They touch at x=-0.5 and x=0.5. Good.

**State management:**

```javascript
const NUM = 3;
const cubieCount = NUM ** 3; // 27
const cubieData = []; // array of { pos, faces }

function createSolvedState() {
  cubieData.length = 0;
  for (let x = -1; x <= 1; x++) {
    for (let y = -1; y <= 1; y++) {
      for (let z = -1; z <= 1; z++) {
        const pos = {x, y, z};
        const faces = {
          front: null, back: null, left: null, right: null,
          top: null, bottom: null
        };
        // Set colors for visible faces
        if (x === 1) faces.right = 'red';
        if (x === -1) faces.left = 'orange';
        if (y === 1) faces.top = 'white';
        if (y === -1) faces.bottom = 'yellow';
        if (z === 1) faces.front = 'green';
        if (z === -1) faces.back = 'blue';
        cubieData.push({pos, faces});
      }
    }
  }
}
```

**Move functions:**

Each move rotates a layer. I need to update the positions and faces of the cubies in that layer.

For example, U move (rotate top layer y=1 clockwise):
- Affected cubies: those with y=1
- New positions: (x, 1, z) → (z, 1, -x)

And the faces rotate too. For example, the right face of a cubie on the top layer becomes the front face after a U move... no wait, that's not right.

Actually, when you rotate a layer, the cubies move, and their faces move with them. So the face colors travel with the cubie. The cubie at (1,1,1) (front-right-top corner) has faces: front=green, right=red, top=white. After U clockwise, it moves to (1,1,-1) (back-right-top). Its faces become: back=green, right=red, top=white.

So I just need to rotate the positions of the cubies, and the face colors stay attached to the cubie. That's the key insight! The "sticker state" is preserved because the face colors move with the cubies.

So the move function is:
1. Identify the cubies in the layer
2. Calculate new positions
3. Update the positions in the state
4. Animate

For animation, I'll interpolate the positions over time.

**Animation:**
I'll use a simple approach: when a move is triggered, start an animation that updates the positions of the affected cubies toward their target positions. During the animation, the move is "in progress" and other moves are disabled.

I'll track:
- `isAnimating`: boolean
- `animatingMove`: the move being animated
- `animProgress`: 0 to 1

During animation, the actual positions are lerped toward targets.

**Camera controls:**
- Mouse drag: when mouse is pressed and moved, rotate the camera around the cube
- Wheel: zoom the camera

I'll implement custom orbit:
- Track mouse down, move, up
- On drag: update camera rotation (yaw/pitch)
- On wheel: adjust camera distance

**Button handlers:**
- U, D, L, R, F, B: quarter turn (90° clockwise)
- Their inverses: U', D', L', R', F', B' (or equivalently 270° clockwise, or 90° counterclockwise)
- Scramble: random sequence of 20 legal moves
- Reset: restore solved state and initial camera

**Scramble:**
A sequence of random moves from {U, D, L, R, F, B} (each applied 90° clockwise). Then Reset.

**Reset:**
- Restore the solved state
- Reset camera to initial position (perspective camera with specific angle and position)

Let me now write the code. I'll make it self-contained in index.html.

Let me think about the exact Three.js API. Since three.min.js is provided globally as THREE, I'll use it directly.

```javascript
// Scene
const scene = new THREE.Scene();

// Camera
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(5, 5, 5);

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

// Lights
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);
```

For the cube, I'll create meshes. Let me define the colors:
- Front: green (#00ff00 or similar)
- Back: blue
- Left: orange
- Right: red
- Top: white
- Bottom: yellow

I'll use standard hex colors:
- Red: #ff0000
- Green: #00ff00
- Blue: #0000ff
- Orange: #ffa500
- White: #ffffff
- Yellow: #ffff00

Now, let me write the complete file.

Actually, let me reconsider the animation. Instead of complex interpolation, I could do a simple swap animation where the cubies visually move. But smooth rotation looks better.

For the animation, I'll use requestAnimationFrame and update positions based on elapsed time.

Let me structure the code:

```javascript
// ============ CUBE STATE ============
const NUM = 3;
let cubies = []; // array of { pos: {x,y,z}, faces: {...} }

function initCubies() {
  // create solved state
}

// ============ MOVE LOGIC ============
function moveLayer(layer, axis, direction) {
  // layer: 'x', 'y', or 'z'
  // axis: which axis to rotate around
  // direction: 1 for clockwise, -1 for counterclockwise
  // returns updated cubie positions
}

// Actually, let me define specific move functions
function uClockwise() { ... }
function uCounterClockwise() { ... }
// etc.
```

For the move functions, I'll implement the position rotation and update the state.

Let me write the rotation logic:

```javascript
// U: rotate y=1 layer, clockwise looking from top
// (x, 1, z) -> (z, 1, -x)
function rotateU(cubies) {
  const temp = [];
  for (const c of cubies) {
    if (c.pos.y !== 1) continue;
    const newX = c.pos.z;
    const newZ = -c.pos.x;
    temp.push({...c, pos: {x: newX, y: 1, z: newZ}});
  }
  return temp;
}
```

But I also need to handle the inverse moves. U' is counterclockwise, which is the opposite rotation: (x, 1, z) -> (-z, 1, x).

Similarly for other moves.

OK, let me write the full code now. I'll create a comprehensive index.html.

Let me think about the UI layout:
- Canvas takes up most of the screen
- Controls panel at the bottom or side
- Buttons: U, U', D, D', L, L', R, R', F, F', B, B', Scramble, Reset

That's a lot of buttons. Maybe group them: U/D/L/R/F/B with their primed versions as secondary buttons, plus Scramble and Reset.

Let me design the HTML:

```html
<div id="container">
  <div id="cube-container"></div>
  <div id="controls">
    <div class="row">
      <button data-move="U">U</button>
      <button data-move="U'">U'</button>
      ...
    </div>
    <button id="scramble">Scramble</button>
    <button id="reset">Reset</button>
  </div>
</div>
```

Now, let me write the complete file. I'll be careful with the Three.js usage.

For the cube geometry, I'll create a BoxGeometry for each cubie. Size 1, with colors.

To show the boundaries clearly, I might add a slight outline or just rely on the color differences.

Let me also add a grid or some reference. Actually, the cube itself is the reference.

One more thing: the requirement says "drag the background orbits the camera without turning a face." This means clicking and dragging on the background (not on the cube) should orbit the camera. I'll implement this by tracking mouse events on the canvas.

Let me finalize the code structure:

```javascript
// --- Setup ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 100);
camera.position.set(5, 5, 5);
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

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

// --- Cube State ---
const NUM = 3;
let cubies = [];

function createSolvedCubies() {
  cubies = [];
  for (let x = -1; x <= 1; x++) {
    for (let y = -1; y <= 1; y++) {
      for (let z = -1; z <= 1; z++) {
        cubies.push({
          pos: {x, y, z},
          faces: {
            front: null, back: null, left: null, right: null,
            top: null, bottom: null
          }
        });
      }
    }
  }
  // Set colors
  for (const c of cubies) {
    if (c.pos.x === 1) c.faces.right = '#ff0000';
    if (c.pos.x === -1) c.faces.left = '#ffa500';
    if (c.pos.y === 1) c.faces.top = '#ffffff';
    if (c.pos.y === -1) c.faces.bottom = '#ffff00';
    if (c.pos.z === 1) c.faces.front = '#00ff00';
    if (c.pos.z === -1) c.faces.back = '#0000ff';
  }
}

// --- Build meshes ---
let meshGroup;
function buildMesh() {
  meshGroup = new THREE.Group();
  for (const c of cubies) {
    const geo = new THREE.BoxGeometry(1, 1, 1);
    const mat = new THREE.MeshStandardMaterial({ 
      color: getFaceColor(c.faces),
      metalness: 0.1,
      roughness: 0.5
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.set(c.pos.x, c.pos.y, c.pos.z);
    meshGroup.add(mesh);
  }
  scene.add(meshGroup);
}

function getFaceColor(faces) {
  // Return a single color for the cubie's primary face
  if (faces.right) return faces.right;
  if (faces.left) return faces.left;
  if (faces.top) return faces.top;
  if (faces.bottom) return faces.bottom;
  if (faces.front) return faces.front;
  if (faces.back) return faces.back;
  return '#888888';
}
```

Hmm, using a single color per cubie might not show all the face colors. But for a solved cube, the outer surface will show the correct colors. The inner cubies (hidden) don't matter much.

Actually, for a better visual, I could create multiple meshes per cubie (one per face). But that's a lot of draw calls (26 cubies × up to 3 faces each ≈ 60 meshes). It's fine for performance.

Let me try a simpler approach first: one box per cubie with the primary face color. This will show the 6 face colors on the outside. The boundaries between cubies are visible since they touch.

For the move animation, I'll animate the positions of the cubies in the layer.

Let me write the complete code now. I'll focus on getting it working correctly.

**Animation details:**
- When a move is triggered, set `isAnimating = true`
- Store the move type and target positions
- In the render loop, if animating, lerp positions toward targets
- When done, set `isAnimating = false`

**Camera orbit:**
- On mousedown: record start position
- On mousemove: compute delta, rotate camera around origin
- On mouseup: stop orbiting
- On wheel: zoom (adjust camera distance)

Initial camera position: (5, 5, 5) with fov 50.

Let me write the code. I'll make it robust.

Actually, I realize I should test the code in the browser. Let me first create the file, then run it.

Let me write the complete index.html:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rubik's Cube</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { overflow: hidden; background: #222; font-family: Arial, sans-serif; }
  #canvas-container { width: 100vw; height: 100vh; display: block; }
  #controls {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0,0,0,0.7);
    padding: 12px 24px;
    border-radius: 8px;
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: center;
  }
  .btn {
    padding: 8px 14px;
    background: #333;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.2s;
  }
  .btn:hover { background: #555; }
  .btn:active { background: #444; }
  .btn.primary { background: #4CAF50; }
  .btn.primary:hover { background: #45a049; }
  .row { display: flex; gap: 4px; }
  #info {
    position: fixed;
    top: 10px;
    left: 10px;
    color: #ccc;
    font-size: 12px;
    background: rgba(0,0,0,0.5);
    padding: 8px 12px;
    border-radius: 4px;
  }
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="info">Click and drag to orbit the camera. Press buttons to manipulate the cube.</div>
<div id="controls">
  <div class="row">
    <button class="btn" data-move="U">U</button>
    <button class="btn" data-move="U'">U'</button>
  </div>
  <div class="row">
    <button class="btn" data-move="D">D</button>
    <button class="btn" data-move="D'">D'</button>
  </div>
  <div class="row">
    <button class="btn" data-move="L">L</button>
    <button class="btn" data-move="L'">L'</button>
  </div>
  <div class="row">
    <button class="btn" data-move="R">R</button>
    <button class="btn" data-move="R'">R'</button>
  </div>
  <div class="row">
    <button class="btn" data-move="F">F</button>
    <button class="btn" data-move="F'">F'</button>
  </div>
  <div class="row">
    <button class="btn" data-move="B">B</button>
    <button class="btn" data-move="B'">B'</button>
  </div>
  <button class="btn primary" id="scramble">Scramble</button>
  <button class="btn primary" id="reset">Reset</button>
</div>

<script src="three.min.js"></script>
<script>
// ... JavaScript ...
</script>
</body>
</html>
```

Now the JavaScript. Let me write it carefully.

```javascript
'use strict';

// ============ CUBE STATE ============
const NUM = 3;
let cubies = [];
let meshGroup;
let isAnimating = false;
let animatingMove = null;
let animProgress = 0;
const ANIM_DURATION = 300; // ms
let animStartTime = 0;

// Colors
const COLORS = {
  front: '#00ff00',
  back: '#0000ff',
  left: '#ffa500',
  right: '#ff0000',
  top: '#ffffff',
  bottom: '#ffff00'
};

// ============ INITIALIZATION ============
function createSolvedCubies() {
  cubies = [];
  for (let x = -1; x <= 1; x++) {
    for (let y = -1; y <= 1; y++) {
      for (let z = -1; z <= 1; z++) {
        cubies.push({
          pos: {x, y, z},
          faces: {
            front: null, back: null, left: null, right: null,
            top: null, bottom: null
          }
        });
      }
    }
  }
  // Assign colors to visible faces
  for (const c of cubies) {
    if (c.pos.x === 1) c.faces.right = COLORS.right;
    if (c.pos.x === -1) c.faces.left = COLORS.left;
    if (c.pos.y === 1) c.faces.top = COLORS.top;
    if (c.pos.y === -1) c.faces.bottom = COLORS.bottom;
    if (c.pos.z === 1) c.faces.front = COLORS.front;
    if (c.pos.z === -1) c.faces.back = COLORS.back;
  }
}

function rebuildMesh() {
  meshGroup = new THREE.Group();
  for (const c of cubies) {
    const geo = new THREE.BoxGeometry(1, 1, 1);
    const mat = new THREE.MeshStandardMaterial({
      color: c.faces.right || c.faces.left || c.faces.top || c.faces.bottom || c.faces.front || c.faces.back || '#555555',
      metalness: 0.1,
      roughness: 0.5
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.set(c.pos.x, c.pos.y, c.pos.z);
    meshGroup.add(mesh);
  }
  scene.add(meshGroup);
}

// ============ MOVE LOGIC ============

// Rotate a layer and return new positions
function rotateLayer(layer, axis, clockwise) {
  const result = [];
  for (const c of cubies) {
    if (axis === 'y' && c.pos.y !== 1) continue;
    if (axis === 'y' && c.pos.y !== -1) continue;
    if (axis === 'x' && c.pos.x !== -1) continue;
    if (axis === 'x' && c.pos.x !== 1) continue;
    if (axis === 'z' && c.pos.z !== 1) continue;
    if (axis === 'z' && c.pos.z !== -1) continue;
    
    let nx = c.pos.x, ny = c.pos.y, nz = c.pos.z;
    if (clockwise) {
      if (axis === 'y') { const tmp = nx; nx = nz; nz = -tmp; }
      if (axis === 'x') { const tmp = ny; ny = -nz; nz = tmp; }
      if (axis === 'z') { const tmp = nx; nx = -ny; ny = tmp; }
    } else {
      if (axis === 'y') { const tmp = nx; nx = -nz; nz = tmp; }
      if (axis === 'x') { const tmp = ny; ny = nz; nz = -tmp; }
      if (axis === 'z') { const tmp = nx; nx = ny; ny = -tmp; }
    }
    result.push({...c, pos: {x: nx, y: ny, z: nz}});
  }
  return result;
}

function applyMove(moveName) {
  if (isAnimating) return;
  
  // Determine layer and axis
  let axis, clockwise;
  switch(moveName) {
    case 'U': axis = 'y'; clockwise = true; break;
    case 'U\'': axis = 'y'; clockwise = false; break;
    case 'D': axis = 'y'; clockwise = false; break; // D is clockwise from bottom = counterclockwise from top
    case 'D\'': axis = 'y'; clockwise = true; break;
    case 'L': axis = 'x'; clockwise = true; break;
    case 'L\'': axis = 'x'; clockwise = false; break;
    case 'R': axis = 'x'; clockwise = false; break;
    case 'R\'': axis = 'x'; clockwise = true; break;
    case 'F': axis = 'z'; clockwise = true; break;
    case 'F\'': axis = 'z'; clockwise = false; break;
    case 'B': axis = 'z'; clockwise = true; break;
    case 'B\'': axis = 'z'; clockwise = false; break;
    default: return;
  }
  
  const newCubies = rotateLayer(cubies, axis, clockwise);
  
  // Update state
  cubies = newCubies;
  
  // Animate
  isAnimating = true;
  animatingMove = moveName;
  animStartTime = performance.now();
  
  // Rebuild mesh with new positions
  rebuildMesh();
}

// ============ CAMERA CONTROLS ============
let isDragging = false;
let lastMouseX = 0, lastMouseY = 0;
let cameraAngle = 0;
let cameraDistance = 5;

function updateCamera(delta) {
  // Orbit: rotate camera around origin based on drag
  if (isDragging) {
    const dx = mouseX - lastMouseX;
    const dy = mouseY - lastMouseY;
    cameraAngle += dx * 0.005;
    // Simple pitch
    // ...
  }
  // Zoom
  camera.distance = Math.max(2, Math.min(15, cameraDistance));
}

// ============ RENDER LOOP ============
function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min(1, (time - (animStartTime || time)) / ANIM_DURATION);
  if (isAnimating) {
    animProgress = Math.min(1, dt);
    // Interpolate positions
    // ...
    if (animProgress >= 1) {
      isAnimating = false;
      animatingMove = null;
    }
  }
  renderer.render(scene, camera);
}
```

Hmm, the animation interpolation needs more thought. Let me simplify: during animation, I'll lerp the positions of the cubies being moved.

Actually, for a cleaner implementation, let me separate the animation into two phases:
1. Before animation: update positions instantly
2. During animation: lerp toward target positions
3. After animation: done

But since I'm rebuilding the mesh every frame anyway (rebuildMesh creates new meshes), I can just update the positions of the existing meshes.

Let me restructure:

```javascript
// In the main loop:
if (isAnimating) {
  const t = animProgress;
  for (const c of cubies) {
    // c.pos is already updated to the target (from rotateLayer)
    // We need to lerp from old pos to new pos
  }
}
```

Actually, the issue is that `cubies` is updated to the new positions immediately. For animation, I need to keep the old positions and interpolate.

Let me store the old positions before the move:

```javascript
function applyMove(moveName) {
  if (isAnimating) return;
  
  // Save old positions
  const oldCubies = cubies.map(c => ({...c}));
  
  // Compute new positions
  const newCubies = rotateLayer(cubies, axis, clockwise);
  
  // Animate
  isAnimating = true;
  animatingMove = moveName;
  animStartTime = performance.now();
  oldPositions = oldCubies.map(c => ({...c.pos}));
  
  rebuildMesh(); // rebuild with new positions
}
```

Then in the render loop:
```javascript
if (isAnimating) {
  const elapsed = performance.now() - animStartTime;
  const t = Math.min(1, elapsed / ANIM_DURATION);
  // Interpolate positions of cubies in the move
  for (let i = 0; i < cubies.length; i++) {
    const old = oldPositions[i];
    const newC = newCubies[i]; // need to store this
    cubies[i].pos.x = old.pos.x + (newC.pos.x - old.pos.x) * t;
    // ...
  }
  if (t >= 1) {
    isAnimating = false;
    animatingMove = null;
  }
}
```

This is getting complicated. Let me simplify the animation: just do a quick swap animation where the cubies visually jump to their new positions over 200ms. Use a simple lerp.

Actually, for the purpose of this task, the animation doesn't need to be perfect. Let me implement a basic version:

1. When a move is triggered, set `isAnimating = true`
2. In the render loop, if animating, lerp the positions of the cubies in the moved layer
3. When the animation completes, set `isAnimating = false`

Let me store the moved cubies' indices and their target positions.

Here's a cleaner approach:

```javascript
let moveTargetCubies = []; // indices of cubies being moved

function applyMove(moveName) {
  if (isAnimating) return;
  
  const oldCubies = cubies.map(c => ({...c}));
  const newCubies = rotateLayer(cubies, axis, clockwise);
  
  // Find which cubies changed
  moveTargetCubies = [];
  for (let i = 0; i < cubies.length; i++) {
    if (JSON.stringify(oldCubies[i]) !== JSON.stringify(newCubies[i])) {
      moveTargetCubies.push(i);
    }
  }
  
  cubies = newCubies;
  isAnimating = true;
  animStartTime = performance.now();
  
  rebuildMesh();
}
```

In the render loop:
```javascript
if (isAnimating) {
  const elapsed = performance.now() - animStartTime;
  const t = Math.min(1, elapsed / ANIM_DURATION);
  for (const idx of moveTargetCubies) {
    const oldPos = oldCubies[idx].pos;
    const newPos = cubies[idx].pos;
    cubies[idx].pos.x = oldPos.x + (newPos.x - oldPos.x) * t;
    cubies[idx].pos.y = oldPos.y + (newPos.y - oldPos.y) * t;
    cubies[idx].pos.z = oldPos.z + (newPos.z - oldPos.z) * t;
  }
  if (t >= 1) {
    isAnimating = false;
    moveTargetCubies = [];
  }
}
```

This should work. The mesh positions are updated via rebuildMesh, but the actual positions used for rendering come from the mesh.position, which I set from cubies.pos. So I need to update mesh.position too, or rebuild the mesh each frame.

Actually, rebuildMesh creates new meshes every frame, which is wasteful but works. The mesh.position is set from cubies.pos in rebuildMesh. So during animation, the new meshes have the target positions. But the old meshes still have old positions. Since I'm rebuilding every frame, the scene always shows the latest mesh.

Wait, if I call rebuildMesh() every frame, the scene gets the new mesh with the target positions. But during animation, I want the intermediate positions to be shown. If I rebuild with the target positions every frame, the animation won't be visible (it'll snap).

Better approach: don't rebuild the mesh every frame. Instead, update the position of existing meshes:

```javascript
function rebuildMesh() {
  meshGroup.clear();
  for (const c of cubies) {
    const geo = new THREE.BoxGeometry(1, 1, 1);
    const mat = new THREE.MeshStandardMaterial({...});
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.set(c.pos.x, c.pos.y, c.pos.z);
    meshGroup.add(mesh);
  }
}
```

If I call this every frame with the updated cubies.pos, the mesh will be at the current position. During animation, I update cubies.pos with lerp values, and rebuildMesh will reflect that.

OK, let me finalize the code. I'll write it all out now.

Actually, I realize there might be an issue with the move directions for D. Let me double-check:

- U: clockwise looking from top (+y). Top layer (y=1) rotates: (x, 1, z) → (z, 1, -x)
- D: clockwise looking from top. Bottom layer (y=-1) rotates: (x, -1, z) → (-z, -1, x)

Wait, is D clockwise from top the same as counterclockwise from bottom? Yes. And the mapping (x, -1, z) → (-z, -1, x) is correct for a clockwise rotation of the bottom face when viewed from above.

Let me verify with a point: bottom-front-right (1, -1, 1). After D clockwise, it should go to bottom-front-left? Hmm, let me think. Looking from top, the bottom face appears rotated clockwise. The front-right corner goes to front-left. So (1, -1, 1) → (-1, -1, 1)? Using the formula: x' = -z = -1, z' = x = 1. So (-1, -1, 1). Yes, that's front-left-bottom. Correct.

OK, I'm confident in the rotations.

Now, let me also think about the inverse moves:
- U' is counterclockwise from top: (x, 1, z) → (-z, 1, x)
- D' is counterclockwise from top (or clockwise from bottom): (x, -1, z) → (z, -1, -x)
- L': (x=-1 layer): (-1, y, z) → (-z, y, 1)
- R': (x=1 layer): (1, y, z) → (z, y, -1)
- F': (z=1 layer): (x, y, 1) → (y, -x, 1)
- B': (z=-1 layer): (x, y, -1) → (-y, x, -1)

These match my earlier derivation.

Now, let me write the complete file. I'll make sure everything is correct.

For the scramble, I'll generate a random sequence of 20 moves from [U, D, L, R, F, B] (each applied as a quarter turn clockwise).

For reset: call createSolvedCubies() and rebuildMesh(), and reset camera to initial position.

Initial camera: position (5, 5, 5), target: (0, 0, 0) center.

Let me write the final code now.

One more consideration: the cube should be fully visible initially. With the camera at (5,5,5) and the cube centered at origin with extent ~1.5, it should be visible. I might need to adjust the initial camera position to ensure good visibility.

Also, for the orbit controls: I'll implement a simple system where:
- On mouse down on the canvas: start dragging
- On mouse move: update camera rotation (yaw)
- On mouse up: stop dragging
- On wheel: adjust camera distance (zoom)

For the orbit, I'll rotate the camera around the origin. I'll track the camera's current position and orientation.

Let me use a simple approach:
- Keep camera at a fixed distance from origin
- Rotate the camera around the y-axis (yaw) based on drag
- Also allow some pitch

```javascript
let camYaw = 0;
let camPitch = 0;

function onMouseDown(e) {
  isDragging = true;
  lastX = e.clientX;
  lastY = e.clientY;
}

function onMouseMove(e) {
  if (!isDragging) return;
  const dx = e.clientX - lastX;
  const dy = e.clientY - lastY;
  camYaw += dx * 0.01;
  camPitch += dy * 0.01;
  lastX = e.clientX;
  lastY = e.clientY;
}

function onMouseUp() {
  isDragging = false;
}

function updateCamera() {
  const dist = cameraDistance;
  const x = Math.sin(camYaw) * dist;
  const y = Math.sin(camPitch) * dist;
  const z = Math.cos(camYaw) * dist;
  camera.position.set(x, y, z);
  camera.lookAt(0, 0, 0);
}
```

This should work for basic orbit.

Now, let me put it all together in the index.html
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 an interactive 3D 3x3 Rubik's Cube using Three.js. The three.min.js is provided in the directory. We need to write index.html with inline CSS and JS, no external resources.

Let me plan the implementation:

1. Load three.min.js via a script tag (global THREE)
2. Create a 3x3 Rubik's cube made of small cubies (each face has 9 stickers)
3. Each cubie is a small cube with colored faces
4. Implement U, D, L, R, F, B quarter turns (90 degrees)
5. Provide buttons for each move and inverse
6. Camera orbit (drag to rotate camera around cube) - custom orbit without using OrbitControls addon
7. Scramble button: apply a sequence of legal turns
8. Reset button: restore solved state and initial camera

Key requirements:
- Start solved with six distinct face colors and visible cubie boundaries
- Preserve cubie and sticker state through moves
- Visible buttons for U, D, L, R, F, B quarter turns and their inverses
- Animate each move and queue/disable further moves until complete
- Drag background orbits camera without turning face (custom orbit)
- Scramble and Reset
- Verify: turn one face and its inverse, check stickers return; orbit camera, then Reset

Let me design the cube:

A 3x3 cube has 26 visible cubies (8 corners + 12 edges + 6 centers). But for simplicity, we can model it as a single mesh per face with colored stickers, or as individual small cubes.

Actually, the simplest approach: create a cube for each face (6 faces), each face is a 3x3 grid of small squares (stickers). But we need to track cubie state - when you rotate a face, the stickers on that face move, and adjacent edge/corner pieces also move.

Alternative: Build the cube from individual small cube pieces (cubies). Each cubie has up to 3 colored faces. When rotating a face, we rotate the pieces.

Let me think about the data structure:

For a 3x3 cube, there are:
- 8 corner cubies: positions (0,0,0), (0,0,2), (0,2,0), (0,2,2), (2,0,0), (2,0,2), (2,2,0), (2,2,2)
- 12 edge cubies: positions like (0,1,0), (0,1,2), (1,0,0), etc.
- 6 center cubies: (1,1,0), (1,1,2), (1,0,1), (1,2,1), (0,1,1), (2,1,1)

Each cubie has a color (for solved state):
- U (up): white
- D (down): yellow
- F (front): green
- B (back): blue
- L (left): orange
- R (right): red

In standard coloring: U=white, D=yellow, F=green, B=blue, L=orange, R=red.

For the solved state, each face has uniform color.

To implement moves, I'll use a group-based approach:
- Create a group for each face's stickers? Or use a single mesh with multiple materials?

Simplest: Use a single Mesh for the entire cube surface, but that makes it hard to animate individual cubie movements.

Better approach: Create 20 visible pieces (8 corners + 12 edges + 6 centers) as small boxes. Each box has a color on each face. When a move happens, we rotate the relevant pieces.

Actually, let me simplify: Since this is a visual demo, I can create the cube from 6 face plates (each a 3x3 grid of colored squares). For the "cubie" concept, I can track the state of each sticker position.

Hmm, but the requirement says "preserve cubie and sticker state through moves; recoloring a static cube is not sufficient." This means we need actual piece movement, not just recoloring.

Let me go with the piece-based approach:

Create small cubes (size 1) for each cubie position. Each cubie has a color attribute for each of its 3 visible faces.

For the solved state:
- Corner cubies: each has 3 colors matching the faces they belong to
- Edge cubies: each has 2 colors
- Center cubies: each has 1 color

When rotating a face (e.g., U face), the top layer of cubies rotates:
- Corners: (0,0,2), (0,2,2), (2,2,2), (2,0,2) rotate among themselves
- Edges: (0,1,2), (1,2,2), (2,1,2), (1,0,2) rotate among themselves
- Centers: (1,1,2) stays in place (but rotates visually)

For the animation, I'll use THREE.AnimationGroup or simply interpolate positions/rotations over time.

Let me design the data:

Cube positions are indexed by (x, y, z) where x,y,z ∈ {0,1,2}.

Corners (8):
- (0,0,0): UFL - white, green, orange → actually depends on orientation
- Let me define: U=white, D=yellow, F=green, B=blue, L=orange, R=red

Corner at (0,0,0) = UFL: colors white (U), green (F), orange (L)
Corner at (0,0,2) = UFR: white, red, green
Corner at (0,2,0) = UBL: white, orange, blue
Corner at (0,2,2) = UBR: white, blue, red
Corner at (2,0,0) = DFL: yellow, green, orange
Corner at (2,0,2) = DFR: yellow, red, green
Corner at (2,2,0) = DBL: yellow, orange, blue
Corner at (2,2,2) = DBR: yellow, blue, red

Edges (12):
- (0,1,0): UL - white, orange
- (0,1,2): UR - white, red
- (1,0,0): FL - green, orange
- (1,0,2): FR - green, red
- (1,2,0): BL - blue, orange
- (1,2,2): BR - blue, red
- (2,1,0): DL - yellow, orange
- (2,1,2): DR - yellow, red
- (0,2,1): LF - orange, blue... wait, (0,2,1) is left-back edge: L and B → orange, blue
- (0,2,1): actually y=2 is back, so (0,2,1) = LB: orange, blue
- (2,2,1): RB: red, blue
- (1,1,0): FU... no wait. Edge at (1,1,0) is front-center: F: green
- (1,1,2): B: blue
- (1,0,1): FL... no, (1,0,1) is front-middle: green
- (1,2,1): back-middle: blue
- (0,1,1): UL... no, (0,1,1) is left-middle: orange
- (2,1,1): right-middle: red

Centers (6):
- (1,1,0): U: white
- (1,1,2): D: yellow
- (1,0,1): F: green
- (1,2,1): B: blue
- (0,1,1): L: orange
- (2,1,1): R: red

Now, for each move, I need to update the positions and colors of the affected pieces.

Let me define the pieces as objects:
```js
class Cubie {
  constructor(x, y, z, colors) {
    this.position = new THREE.Vector3(x, y, z);
    this.colors = colors; // array of [color, color] for edges, [color,color,color] for corners, [color] for centers
    this.quaternion = new THREE.Quaternion();
  }
}
```

For animation, I'll use a simple approach: when a move is triggered, I'll set a flag that the move is in progress, queue the move, and animate it using a tween (I can implement a simple linear interpolation or use a requestAnimationFrame loop).

Since we can't use external libraries, I'll implement a simple animation system:
- Each move has a start time, duration, and target state
- In the render loop, if a move is in progress, interpolate positions/rotations
- Disable input during animation

Let me think about the move transformations:

**U move (rotate top face clockwise 90°):**
- Top layer: z=2
- Corners: (0,0,2)→(0,2,2)→(2,2,2)→(2,0,2)→(0,0,2)
- Edges: (0,1,2)→(1,2,2)→(2,1,2)→(1,0,2)→(0,1,2)
- Centers: (1,1,2) stays

**D move (rotate bottom face clockwise 90° looking from below, i.e., z=0):**
- Corners: (0,0,0)→(0,2,0)→(2,2,0)→(2,0,0)→(0,0,0)
- Edges: (0,1,0)→(1,2,0)→(2,1,0)→(1,0,0)→(0,1,0)
- Centers: (1,1,0) stays

**F move (rotate front face clockwise 90° looking from front):**
- Front layer: y=0
- Corners: (0,0,0)→(0,0,2)→(2,0,2)→(2,0,0)→(0,0,0)
- Edges: (0,0,1)→(2,0,1)→(2,0,2)... wait, edges on front face: (0,0,1) is FL, (1,0,0) is FL... hmm.

Let me be more careful. Front face is y=0. Pieces on front face:
- Corners: (0,0,0), (0,0,2), (2,0,2), (2,0,0)
- Edges: (0,0,1), (1,0,0), (1,0,2), (2,0,1)
- Center: (1,0,1)

F clockwise rotation (looking at front face): 
- Corners: (0,0,0)→(2,0,0)→(2,0,2)→(0,0,2)→(0,0,0)
- Edges: (0,0,1)→(1,0,0)→(2,0,2)? No...

Let me think in terms of coordinates. A clockwise rotation of the front face (y=0) maps:
- (x, 0, z) → (z, 0, 2-x) ... let me verify: (0,0,0) → (0,0,2) ✓; (0,0,2) → (2,0,2) ✓; (2,0,2) → (2,0,0) ✓; (2,0,0) → (0,0,0) ✓

So the mapping is: (x, 0, z) → (z, 0, 2-x)

Edges on front face: (0,0,1) → (1,0,0); (1,0,0) → (2,0,2)? No wait: (1,0,0) → (0,0,1)? Let me apply the formula: (1,0,0) → (0,0, 2-1) = (0,0,1). And (0,0,1) → (1,0,0). So (0,0,1) ↔ (1,0,0) swap. And (2,0,1) → (1,0,2); (1,0,2) → (2,0,1).

So edges cycle: (0,0,1) → (1,0,0) → (2,0,1) → (1,0,2) → (0,0,1)

**B move (back face, z=2... no, back is y=2):**
Back face is y=2. Clockwise looking from back (i.e., from positive y towards origin):
- Mapping: (x, 2, z) → (2-z, 2, x)
- Check: (0,2,0) → (2,2,0) ✓; (2,2,0) → (2,2,2) ✓; (2,2,2) → (0,2,2) ✓; (0,2,2) → (0,2,0) ✓

**R move (right face, x=2):**
- Mapping: (2, y, z) → (2, z, 2-y)
- Corners: (2,0,0)→(2,0,2)→(2,2,2)→(2,2,0)→(2,0,0)
- Edges: (2,0,1)→(2,1,0)→(2,2,1)→(2,1,2)→(2,0,1)

**L move (left face, x=0):**
- Mapping: (0, y, z) → (0, 2-z, 2-y)
- Corners: (0,0,0)→(0,2,2)→(0,2,0)→(0,0,2)→(0,0,0)... hmm, let me verify: (0,0,0) → (0, 2-0, 2-0) = (0,2,2) ✓; (0,2,2) → (0, 2-2, 2-2) = (0,0,0) ✓. Good.

OK, now for the implementation:

I'll create the cube with 26 pieces (8 corners, 12 edges, 6 centers). Each piece is a small box (size 1) positioned at integer coordinates.

For the visual appearance, I'll use a wireframe or solid boxes with colors. To make it look like a Rubik's cube, I'll use solid colors for each face of each cubie.

For corners, each has 3 faces. I can either:
1. Make each cubie a small cube with a single color (the color of the face it's on) — but then corners would lose their multi-colored appearance
2. Make each cubie have different colored faces

For a proper Rubik's cube look, corners should show 3 colors. I can do this by drawing the faces of each cubie with different colors. Since we're using Three.js, I can create a mesh for each face of each cubie, or use a single mesh with multiple materials.

Simpler approach: Create the cube from 6 face plates (each a 3x3 grid of colored squares). Each plate is a plane with 9 colored rectangles. But then moving pieces is harder.

Alternative: Use a single mesh for the whole cube surface with texture mapping. But that's complex.

Let me go with the piece-based approach where each cubie is a small box, and I color each face of the box according to which face of the cube it's on. For example, a corner cubie at (0,0,2) has faces: U (white), F (green), L (orange). I'll color the three visible faces of the box accordingly.

Actually, for a simpler visual: each cubie is a small cube. I'll color the entire cubie with the primary color of the face it belongs to. For corners, I'll color them based on the dominant face... no, that won't look right.

Let me try a different approach: Create the cube as a collection of small faces (stickers) attached to a central structure. Each sticker is a small plane. When a move happens, I permute the stickers.

This is getting complex. Let me simplify:

**Visual design:** The cube will be built from 6 face groups. Each face is a 3x3 grid of small colored squares (stickers). The stickers are positioned at the correct locations on the cube surface. When a face rotates, the stickers on that face rotate, and the adjacent edge/corner stickers also move.

Actually, the simplest robust approach for a demo:

1. Create a central invisible cube structure
2. For each of the 54 stickers (9 per face × 6 faces), create a small plane/mesh
3. Position them correctly on the cube surface
4. Track which sticker is at which position
5. On a face turn, rotate the stickers on that face and the adjacent ones

But tracking 54 stickers and their positions is error-prone.

**Simpler approach that still satisfies the requirements:**

Build the cube from 20 visible pieces (8 corners + 12 edges). Each piece is a small box. Color each piece with the color of the face it primarily belongs to:
- Corners: color based on the U/D face (white/yellow) since that's most prominent
- Actually, for a solved cube, each corner should show 3 colors. Let me try to make the boxes show multiple colors.

I think the cleanest way is: each cubie is a small box. I'll draw each face of the box with the appropriate color. For example, a corner cubie at (0,0,2) has its U-face (top), F-face (front), and L-face (left) colored white, green, orange respectively. I can do this by creating a box and applying different materials to different faces... but that's complex with a single mesh.

Alternative: Create the cubie as a box, and use a custom shader or multiple meshes. Too complex.

**Pragmatic approach:** Each cubie is a small box with a single color. The solved state shows the 6 face colors clearly. The "cubie boundaries" are visible because the boxes are separate. When you turn a face, the boxes move. The colors on each box represent the face they're on (for centers) or the primary face color (for corners/edges).

This might not perfectly match "six distinct face colors and visible cubie boundaries" but it should be acceptable. The key requirement is that the state is preserved through moves (pieces move, not just colors recolored).

Actually, let me reconsider. The requirement says "six distinct face colors and visible cubie boundaries." So the cube should have 6 distinct face colors. The cubie boundaries should be visible — meaning the pieces should be separated, not merged into a single surface.

I'll create the cube with 20 pieces (8 corners, 12 edges), each as a small box of size 1, positioned at integer coordinates. Each piece will have a color attribute. For the solved state:
- Corners: white/green/orange etc. — I'll assign each corner a color based on its position. For example, the corner at (0,0,2) gets white (U), green (F), orange (L) — but as a single color, I'll pick white.
  
Hmm, for a better look, let me make each piece have a color that represents the face it's on. For corners, I'll use the U/D color (white for up-facing, yellow for down-facing). For edges, I'll use the color of the edge's primary face. For centers, the face color.

This way, the solved cube shows all 6 colors clearly. The pieces move around, preserving their identity.

Actually, you know what, let me make it look really good. I'll create the cube with 54 small square faces (stickers) arranged on the surface. Each sticker is a thin plane. The stickers are grouped by face. When a face rotates, the stickers on that face rotate, and the adjacent edge/corner stickers also rotate.

No, let me keep it simple and functional. Here's my final plan:

**Piece-based cube:**
- 8 corner pieces: small boxes at (0,0,0) etc., each with a color (white, yellow, green, blue, orange, red)
- 12 edge pieces: small boxes at edge centers, each with a color
- 6 center pieces: small boxes at face centers, each with a color

For the solved state:
- Corners: color based on the U/D face (white for U-facing, yellow for D-facing)
- Edges: color based on the F/B/L/R face
- Centers: the face color

When a move happens, I permute the pieces. The colors move with the pieces.

For animation: I'll use a simple tweening system. Each move has a duration (e.g., 300ms). During the animation, I interpolate the positions and rotations of the affected pieces.

Let me code this up.

First, let me set up the HTML structure:
- Canvas container
- Controls: buttons for U, D, L, R, F, B and their inverses (U', D', L', R', F', B')
- Scramble and Reset buttons
- Info display

Three.js setup:
- Scene, camera (perspective), renderer
- Lighting (ambient + directional)
- Cube pieces

Camera orbit: I'll implement custom orbit controls. The user drags the background (canvas) to orbit the camera around the cube. I'll track mouse down/move and update camera position/orientation accordingly, without using OrbitControls.

Let me write the code now.

```html
<!DOCTYPE html>
<html>
<head>
<style>
  /* styles */
</style>
</head>
<body>
  <div id="container"></div>
  <div id="controls">
    <!-- buttons -->
  </div>
  <script src="three.min.js"></script>
  <script>
    // Three.js code
  </script>
</body>
</html>
```

Let me write the JavaScript:

```javascript
// Initialize Three.js
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(4, 4, 6);

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

// Lights
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);

// Cube pieces
const pieces = [];
const pieceSize = 1;
const cubeSize = 3;

// Colors
const COLORS = {
  white: 0xffffff,
  yellow: 0xffaa00,
  green: 0x00aa00,
  blue: 0x0000ff,
  orange: 0xff6600,
  red: 0xff0000
};

// Define pieces: { type: 'corner'|'edge'|'center', position, colors, quaternion }
// ... build the cube

function createCube() {
  // Corners
  const corners = [
    { pos: [0,0,2], colors: [COLORS.white, COLORS.green, COLORS.orange] }, // UFL
    { pos: [0,0,0], colors: [COLORS.white, COLORS.green, COLORS.orange] }, // DFL... hmm
  ];
}
```

Wait, I need to be careful about the color assignment. Let me define the solved state properly.

For a standard Rubik's cube:
- U (up) = white
- D (down) = yellow
- F (front) = green
- B (back) = blue
- L (left) = orange
- R (right) = red

Solved cube state (piece positions and colors):
- Centers: U center = white, D = yellow, F = green, B = blue, L = orange, R = red
- Corners (position → [U/F/L/R colors]):
  - (0,0,2) UFL: [white, green, orange]
  - (0,0,0) DFL: [yellow, green, orange]
  - (0,2,2) UBR: [white, blue, red]
  - (0,2,0) UBL: [white, orange, blue]
  - (2,0,2) DFR: [yellow, red, green]
  - (2,0,0) DFL: already done
  - (2,2,2) DBR: [yellow, blue, red]
  - (2,2,0) DBL: [yellow, orange, blue]

Hmm, let me list all 8 corners with consistent ordering (U, F, L for the first two faces):
1. (0,0,2) UFL: white, green, orange
2. (0,0,0) DFL: yellow, green, orange
3. (0,2,2) UBR: white, blue, red
4. (0,2,0) UBL: white, orange, blue
5. (2,0,2) DFR: yellow, red, green
6. (2,0,0) DFL: already #2
7. (2,2,2) DBR: yellow, blue, red
8. (2,2,0) DBL: yellow, orange, blue

Edges (position → [face1, face2]):
1. (0,1,2) UR: white, red
2. (0,1,0) UL: white, orange
3. (1,0,2) FR: green, red
4. (1,0,0) FL: green, orange
5. (2,1,2) BR: blue, red
6. (2,1,0) DL: yellow, orange
7. (1,2,2) FR... no: (1,2,2) is back-right edge: blue, red
8. (1,2,0) BL: blue, orange
9. (0,2,1) LB: orange, blue
10. (2,2,1) RB: red, blue
11. (1,1,0) F: green
12. (1,1,2) B: blue

Centers:
1. (1,1,2) U: white
2. (1,1,0) D: yellow
3. (1,0,1) F: green
4. (1,2,1) B: blue
5. (0,1,1) L: orange
6. (2,1,1) R: red

For the piece representation, each piece has a position and a color array. For corners, the color array has 3 entries. For edges, 2 entries. For centers, 1 entry.

During a face rotation, I need to:
1. Identify the pieces on the rotating layer
2. Rotate their positions
3. Update their color arrays (the colors move with the pieces)

For the animation, I'll store the current state of each piece (position, rotation, colors) and interpolate toward the target state.

Let me define the move functions:

```javascript
function rotateFace(pieces, axis, direction) {
  // axis: 'x', 'y', or 'z'
  // direction: 1 for clockwise, -1 for counterclockwise
  // Returns the list of pieces that moved and their new positions
}
```

Actually, let me implement specific move functions for each face:

```javascript
function moveU() { /* rotate top face clockwise */ }
function moveUPrime() { /* rotate top face counterclockwise */ }
// etc.
```

Each move function will:
1. Determine which pieces are affected (corners and edges on the rotating layer)
2. Compute the new positions
3. Animate the transition

For animation, I'll use a simple approach: when a move is requested, if no animation is in progress, start an animation with a duration. The animation updates the positions/rotations of the affected pieces over time. During the animation, input is ignored.

Let me implement a generic animation system:

```javascript
let activeMove = null; // { pieces: [], targetPositions: [], startTime: 0, duration: 300 }

function startMove(moveData) {
  if (activeMove) return; // queue or ignore
  activeMove = moveData;
  // Set up initial state
}

function updateMove(deltaTime) {
  if (!activeMove) return;
  const elapsed = performance.now() - activeMove.startTime;
  const t = Math.min(elapsed / activeMove.duration, 1);
  // Interpolate positions
  ...
  if (t >= 1) {
    // Complete the move
    activeMove = null;
    // Apply final state
  }
}
```

For interpolation, I'll linearly interpolate positions. For rotations, I'll rotate the piece's quaternion.

Actually, for simplicity, I'll just move the pieces to their target positions over the duration. The visual effect will be smooth enough.

Let me think about the piece positions. Each piece is a box of size 1, centered at integer coordinates. For example, the corner at (0,0,2) has its center at (0,0,2). The box extends from (-0.5,-0.5,-0.5) to (0.5,0.5,0.5) relative to its center.

When rotating the top face (z=2), the corner at (0,0,2) moves to (0,2,2). Its box center moves from (0,0,2) to (0,2,2).

For the animation, I'll interpolate the position from start to end.

Now, let me also handle the piece's rotation. When a face rotates, the pieces on that face also rotate. For example, a corner piece on the U face rotates 90° around the Z axis. I need to update the piece's rotation accordingly.

For the visual effect, I'll rotate the piece's quaternion to match the new orientation.

OK, let me start coding. I'll write the complete index.html file.

Let me structure the code:

1. HTML: container div, control panel with buttons
2. CSS: styling for the page and buttons
3. JS:
   - Three.js setup
   - Cube data (pieces array)
   - Move functions
   - Animation system
   - Button handlers
   - Render loop
   - Orbit camera (custom)

For the orbit camera: I'll track mouse down on the canvas, and on mouse move, adjust the camera's position to orbit around the cube's center. The camera should stay at a reasonable distance and not get too close.

Initial camera: positioned at (4, 4, 6) looking at (0, 0, 0).

For orbiting: when the user clicks and drags on the canvas, the camera orbits around the cube. I'll implement this by changing the camera's position in a circle around the origin.

Let me write the code now. I'll aim for a clean, working implementation.

Let me draft the full file:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3D Rubik's Cube</title>
<style>
  body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; background: #222; }
  #container { width: 100vw; height: 100vh; display: block; }
  #controls {
    position: absolute;
    top: 10px;
    left: 10px;
    background: rgba(0,0,0,0.7);
    padding: 15px;
    border-radius: 8px;
    color: white;
    z-index: 10;
  }
  .btn-group { margin-bottom: 8px; }
  .btn {
    display: inline-block;
    padding: 6px 12px;
    margin: 3px;
    border: 1px solid rgba(255,255,255,0.5);
    border-radius: 4px;
    background: rgba(255,255,255,0.1);
    color: white;
    cursor: pointer;
    font-size: 14px;
  }
  .btn:hover { background: rgba(255,255,255,0.2); }
  .btn:disabled { opacity: 0.3; cursor: default; }
  #info {
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0,0,0,0.7);
    padding: 8px 16px;
    border-radius: 4px;
    font-size: 13px;
    z-index: 10;
  }
</style>
</head>
<body>
<div id="container"></div>
<div id="controls">
  <div class="btn-group">
    <button class="btn" id="btnU" onclick="queueMove('U')">U</button>
    <button class="btn" id="btnUprime" onclick="queueMove('U\'')">U'</button>
    <button class="btn" id="btnD" onclick="queueMove('D')">D</button>
    <button class="btn" id="btnDprime" onclick="queueMove('D\'')">D'</button>
    <button class="btn" id="btnL" onclick="queueMove('L')">L</button>
    <button class="btn" id="btnLprime" onclick="queueMove('L\'')">L'</button>
    <button class="btn" id="btnR" onclick="queueMove('R')">R</button>
    <button class="btn" id="btnRprime" onclick="queueMove('R\'')">R'</button>
    <button class="btn" id="btnF" onclick="queueMove('F')">F</button>
    <button class="btn" id="btnFprime" onclick="queueMove('F\'')">F'</button>
    <button class="btn" id="btnB" onclick="queueMove('B')">B</button>
    <button class="btn" id="btnBprime" onclick="queueMove('B\'')">B'</button>
  </div>
  <div class="btn-group">
    <button class="btn" id="btnScramble" onclick="scramble()">Scramble</button>
    <button class="btn" id="btnReset" onclick="reset()">Reset</button>
  </div>
  <div id="info">Click and drag on the cube to orbit the camera.</div>
</div>
<script src="three.min.js"></script>
<script>
// ... JavaScript
</script>
</body>
</html>
```

Now the JavaScript. Let me write it carefully.

```javascript
// === Cube Data ===
const COLORS = {
  white: 0xffffff,
  yellow: 0xffaa00,
  green: 0x00aa00,
  blue: 0x0000ff,
  orange: 0xff6600,
  red: 0xff0000
};

// Piece types
const CENTER = 'center';
const EDGE = 'edge';
const CORNER = 'corner';

// Create the solved cube
let pieces = []; // array of { type, position, colors, quaternion, mesh }

function createCube() {
  pieces = [];
  
  // Helper to create a box mesh
  function makeBox(pos, size, color) {
    const geometry = new THREE.BoxGeometry(size, size, size);
    const material = new THREE.MeshStandardMaterial({ color: color, roughness: 0.8 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(pos[0], pos[1], pos[2]);
    mesh.castShadow = true;
    mesh.receiveShadow = true;
    return mesh;
  }
  
  // Centers (6)
  const centers = [
    { pos: [1,1,2], color: COLORS.white },   // U
    { pos: [1,1,0], color: COLORS.yellow },  // D
    { pos: [1,0,1], color: COLORS.green },   // F
    { pos: [1,2,1], color: COLORS.blue },    // B
    { pos: [0,1,1], color: COLORS.orange },  // L
    { pos: [2,1,1], color:
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 an interactive 3D 3x3 Rubik's Cube 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 the runner will inline it, we can probably reference it via a script tag with src pointing to three.min.js. But the instructions say "use the provided three.min.js" — likely we should include it as a local file in the same directory.

Let me first check what's in the current directory and understand the environment. We have three.min.js already present.

The requirements:
1. Start solved, with six distinct face colors and visible cubie boundaries.
2. Visible buttons for U, D, L, R, F, B quarter turns and their inverses. Animate each move and queue or disable further moves until it completes so state is not corrupted.
3. Dragging the background orbits the camera without turning a face. Keep cube fully visible initially and support bounded wheel zoom.
4. Scramble (sequence of legal turns) and Reset to restore solved cube and initial camera.
5. Only core global THREE is provided; implement camera controls without importing unavailable addons.

We need to write everything in one self-contained index.html file with inline CSS and JS. No external assets, CDNs, network requests.

Since three.min.js is provided locally, we can include it via a script tag: `<script src="three.min.js"></script>`. The runner will inline it. So we just need to reference it.

Let me design the cube:

A 3x3 Rubik's cube has 26 visible cubies (8 corners + 12 edges + 6 centers). Actually, for a standard cube, there are 27 small cubes, but the center of the whole cube is hidden. So visible pieces: 8 corners, 12 edges, 6 face centers = 26 pieces.

Each piece has stickers (colors). We need to display the colors on each sticker. We can model the cube with geometry: each face is a 3x3 grid of small squares. Or we can use a more efficient approach: create the cube from faces (Planes or BoxGeometry with custom materials).

Simplest approach: Build the cube from 6 face planes (THREE.PlaneGeometry) or from a BoxGeometry with colored faces? But a standard Rubik's cube has different colors per face: white (U), yellow (D), red (F), orange (B), green (L), blue (R) — standard color scheme.

For a solved cube:
- U face: white
- D face: yellow
- F face: green
- B face: blue
- L face: orange
- R face: red

We need visible cubie boundaries — meaning the edges between cubies should be visible. We could draw the wireframe of the cube, or show the individual small squares (stickers) with borders.

Approach: Create 6 face geometries (each a 3x3 grid of quads). Each face is a PlaneGeometry with width=3, height=3, but we need to subdivide it into 9 smaller squares. We can use a BoxGeometry? No, that's solid.

Alternative: Use a custom approach — create a group containing 26 small cube meshes (each cubie is a small box with colored faces). That would be 26 boxes, each with 6 faces, but only the outer faces are visible. This is complex.

Simpler: Use PlaneGeometry for each face, subdivided into 9 smaller rectangles. We can create a PlaneGeometry with width=3, height=3, and then subdivide it into 9 segments. Actually, PlaneGeometry doesn't support subdivision directly. But we can create a mesh with a custom geometry: a 3x3 grid of small squares.

Or even simpler: Use a BoxGeometry for the whole cube (size 3x3x3) and then... no, that won't work because we need different colors per face.

Another approach: Create the cube from 6 face materials, each a MeshBasicMaterial with a single color, and arrange them as a hollow cube. But then we lose the individual sticker boundaries.

The requirement says "visible cubie boundaries" — meaning we should see the edges between cubies. We can draw the wireframe of the cube (the 12 edge lines) and also the face boundaries. Or we can make each face a grid of 9 colored squares with thin black borders.

Let me think about what's feasible in a single HTML file with minimal code:

Option A: Build the cube from 26 small box pieces (cubies). Each cubie is a small box with colored faces. For example:
- Corner cubies: 3 colors each
- Edge cubies: 2 colors each
- Center cubies: 1 color each

This is the most accurate representation. We'd create 26 meshes, each a small box (e.g., size 1x1x1) positioned appropriately. The centers of the big cube are at (±1.5, ±1.5, ±1.5) etc. Actually, let's set the overall cube size to 3 units (from -1.5 to 1.5 on each axis).

Cube center at origin. Face centers:
- U (white): z = 1.5
- D (yellow): z = -1.5
- F (green): y = 1.5
- B (blue): y = -1.5
- L (orange): x = -1.5
- R (red): x = 1.5

Corners (8):
- (1.5, 1.5, 1.5) → UFR: white, green, red
- (1.5, 1.5, -1.5) → UFL: white, green, orange
- (1.5, -1.5, 1.5) → DFR: yellow, green, red
- (1.5, -1.5, -1.5) → DFL: yellow, green, orange
- (-1.5, 1.5, 1.5) → UBR: white, blue, red
- (-1.5, 1.5, -1.5) → UBL: white, blue, orange
- (-1.5, -1.5, 1.5) → DBR: yellow, blue, red
- (-1.5, -1.5, -1.5) → DBL: yellow, blue, orange

Edges (12):
- UF: (1.5, 1.5, 0) → white/green border
- UR: (1.5, 0, 1.5) → white/red
- UB: (-1.5, 1.5, 0) → white/blue
- UL: (-1.5, 0, 1.5)? Wait, UL is (-1.5, 1.5, 1.5)... let me redo.

Edges (12):
1. UF: between U and F faces: position (0, 1.5, 1.5)? No. The edge UF is at the top front edge: x from -1.5 to 1.5, y=1.5, z=1.5. Midpoint: (0, 1.5, 1.5).
2. UR: top right: (1.5, 1.5, 0)
3. UB: top back: (0, 1.5, -1.5)
4. UL: top left: (-1.5, 1.5, 0)
5. FR: front right: (1.5, 0, 1.5)
6. BR: back right: (0, 0, 1.5)? No, BR is at y=-1.5, z=1.5: (1.5, -1.5, 1.5)
7. BL: back left: (-1.5, -1.5, 1.5)
8. FL: front left: (-1.5, 0, 1.5)? No, FL is at y=1.5? Wait, F is y=1.5, L is x=-1.5: (-1.5, 1.5, 0)
9. BF: back face front edge: (0, -1.5, -1.5)? No, B is y=-1.5, F is y=1.5... BF is at x=-1.5, z=-1.5: (-1.5, -1.5, -1.5)? Hmm.

Let me systematically list the 12 edges of a cube centered at origin with side length 3:
- Top face edges (y=1.5): UF (x from -1.5 to 1.5, z=1.5), UR (x=1.5, z from -1.5 to 1.5), UB (x from -1.5 to 1.5, z=-1.5), UL (x=-1.5, z from -1.5 to 1.5)
- Bottom face edges (y=-1.5): DF (x from -1.5 to 1.5, z=-1.5), DR (x=1.5, z from -1.5 to 1.5), DB (x from -1.5 to 1.5, z=1.5), DL (x=-1.5, z from -1.5 to 1.5)
- Side face edges connecting top/bottom: FR (y=1.5, z=1.5? No, FR is at x=1.5, y from -1.5 to 1.5, z=1.5? Actually FR is the edge between Front (y=1.5) and Right (x=1.5): (1.5, 1.5, 0))
  - FR: (1.5, 1.5, 0)
  - BR: (1.5, -1.5, 0)
  - BL: (-1.5, -1.5, 0)
  - BF: (-1.5, 1.5, 0)

So the 12 edges:
1. UF: (0, 1.5, 1.5)
2. UR: (1.5, 1.5, 0)
3. UB: (0, 1.5, -1.5)
4. UL: (-1.5, 1.5, 0)
5. FR: (1.5, 1.5, 0)? No wait, FR is (1.5, 1.5, 0) — that's the same as UR? No, UR is (1.5, 1.5, 0) — hmm, UR and FR both have coordinates (1.5, 1.5, 0)? That can't be right.

Let me reconsider. The cube vertices are at (±1.5, ±1.5, ±1.5). The edges connect vertices that differ in exactly one coordinate.

Edges along x-axis (varying x, fixed y,z):
- (1.5, 1.5, 1.5) to (-1.5, 1.5, 1.5): top-front edge, z=1.5, y=1.5 → midpoint (0, 1.5, 1.5) — this is UF
- (1.5, 1.5, -1.5) to (-1.5, 1.5, -1.5): bottom-front edge, z=-1.5, y=1.5 → midpoint (0, 1.5, -1.5) — UB
- (1.5, -1.5, 1.5) to (-1.5, -1.5, 1.5): top-back edge, z=1.5, y=-1.5 → midpoint (0, -1.5, 1.5) — DB
- (1.5, -1.5, -1.5) to (-1.5, -1.5, -1.5): bottom-back edge, z=-1.5, y=-1.5 → midpoint (0, -1.5, -1.5) — DB? No, this is DB.

Edges along y-axis (varying y, fixed x,z):
- (1.5, 1.5, 1.5) to (1.5, -1.5, 1.5): top-right edge, x=1.5, z=1.5 → midpoint (1.5, 0, 1.5) — FR
- (1.5, 1.5, -1.5) to (1.5, -1.5, -1.5): bottom-right edge, x=1.5, z=-1.5 → midpoint (1.5, 0, -1.5) — BR
- (-1.5, 1.5, 1.5) to (-1.5, -1.5, 1.5): top-left edge, x=-1.5, z=1.5 → midpoint (-1.5, 0, 1.5) — FL
- (-1.5, 1.5, -1.5) to (-1.5, -1.5, -1.5): bottom-left edge, x=-1.5, z=-1.5 → midpoint (-1.5, 0, -1.5) — FL? No, this is FL? Actually this is the left edge of the bottom face.

Edges along z-axis (varying z, fixed x,y):
- (1.5, 1.5, 1.5) to (1.5, 1.5, -1.5): top-right-front corner edge? No, this is a vertical edge at x=1.5, y=1.5: midpoint (1.5, 1.5, 0) — RF? Actually this is the edge between U and F faces at the top-right corner: UR
- (1.5, -1.5, 1.5) to (1.5, -1.5, -1.5): bottom-right-front edge: (1.5, -1.5, 0) — FR? No, this is the front-right vertical edge: FR
- (-1.5, 1.5, 1.5) to (-1.5, 1.5, -1.5): top-left-front edge: (-1.5, 1.5, 0) — UL
- (-1.5, -1.5, 1.5) to (-1.5, -1.5, -1.5): bottom-left-front edge: (-1.5, -1.5, 0) — FL? No, this is the front-left vertical edge: FL

So the 12 edges (midpoints):
1. UF: (0, 1.5, 1.5)
2. UR: (1.5, 1.5, 0)
3. UB: (0, 1.5, -1.5)
4. UL: (-1.5, 1.5, 0)
5. FR: (1.5, 0, 1.5)
6. BR: (1.5, 0, -1.5)
7. FL: (-1.5, 0, 1.5)
8. BL: (-1.5, 0, -1.5)
9. DF: (0, -1.5, 1.5)
10. DB: (0, -1.5, -1.5)
11. RF: (1.5, 1.5, 0)? No wait, I already used UR for (1.5, 1.5, 0). Let me recount.

Actually, the 12 edges of a cube:
- Top face: 4 edges — UF, UR, UB, UL
- Bottom face: 4 edges — DF, DR, DB, DL
- Vertical edges: 4 edges — FR, FL, BR, BL

Where:
- UF: (0, 1.5, 1.5)
- UR: (1.5, 1.5, 0)
- UB: (0, 1.5, -1.5)
- UL: (-1.5, 1.5, 0)
- FR: (1.5, 0, 1.5)
- FL: (-1.5, 0, 1.5)
- BR: (1.5, 0, -1.5)
- BL: (-1.5, 0, -1.5)
- DF: (0, -1.5, 1.5)
- DB: (0, -1.5, -1.5)
- RF: (1.5, 1.5, 0)? No, RF is the same as UR... 

Hmm, I'm confusing myself. Let me use a systematic approach. The cube has 12 edges. Each edge connects two vertices that differ in exactly one coordinate by 3 (from -1.5 to 1.5).

Vertices (8):
V1: (1.5, 1.5, 1.5)
V2: (-1.5, 1.5, 1.5)
V3: (-1.5, -1.5, 1.5)
V4: (1.5, -1.5, 1.5)
V5: (1.5, 1.5, -1.5)
V6: (-1.5, 1.5, -1.5)
V7: (-1.5, -1.5, -1.5)
V8: (1.5, -1.5, -1.5)

Edges (12):
1. V1-V2: (0, 1.5, 1.5) — UF
2. V2-V3: (-1.5, 0, 1.5) — UL? No, V2=(-1.5,1.5,1.5) to V3=(-1.5,-1.5,1.5): x=-1.5, z=1.5, y varies → midpoint (-1.5, 0, 1.5) — UL
3. V3-V4: (0, -1.5, 1.5) — UB? V3=(-1.5,-1.5,1.5) to V4=(1.5,-1.5,1.5): y=-1.5, z=1.5 → midpoint (0, -1.5, 1.5) — DB? No wait, this is the back-top edge. Let me label properly:
   - Top face (y=1.5): V1(1.5,1.5,1.5) to V2(-1.5,1.5,1.5) = UF; V2 to V3(-1.5,-1.5,1.5) = UL; V3 to V4(1.5,-1.5,1.5) = UB? No, V3 to V4 goes from back-left to front-right on the top face — that's not an edge of the top face. The top face edges are: V1-V2 (front edge), V2-V3 (left edge), V3-V4? No, V3-V4 is diagonal on the top face. The top face edges are: V1-V2, V2-V3, V3-V8? No.

I'm getting confused. Let me just define the 12 edges by their midpoints:
- Top face (y=1.5): four edges at y=1.5, z=±1.5 (front/back) and x=±1.5 (left/right):
  - UF: y=1.5, z=1.5, x from -1.5 to 1.5 → midpoint (0, 1.5, 1.5)
  - UR: x=1.5, z=1.5, y from -1.5 to 1.5 → midpoint (1.5, 0, 1.5)
  - UB: y=1.5, z=-1.5, x from -1.5 to 1.5 → midpoint (0, 1.5, -1.5)
  - UL: x=-1.5, z=1.5, y from -1.5 to 1.5 → midpoint (-1.5, 0, 1.5)
- Bottom face (y=-1.5):
  - DF: y=-1.5, z=1.5, x from -1.5 to 1.5 → midpoint (0, -1.5, 1.5)
  - DR: x=1.5, z=1.5, y from -1.5 to 1.5 → midpoint (1.5, 0, 1.5)? No, that's the same as UR. DR is at the bottom-right: x=1.5, z=1.5, y=-1.5 → midpoint (1.5, -1.5, 1.5)
  - DB: y=-1.5, z=-1.5, x from -1.5 to 1.5 → midpoint (0, -1.5, -1.5)
  - DL: x=-1.5, z=1.5, y from -1.5 to 1.5 → midpoint (-1.5, 0, 1.5)? No, DL is bottom-left: x=-1.5, z=1.5, y=-1.5 → midpoint (-1.5, -1.5, 1.5)
- Vertical edges (z-axis):
  - FR: x=1.5, y=1.5, z from -1.5 to 1.5 → midpoint (1.5, 1.5, 0)
  - FL: x=-1.5, y=1.5, z from -1.5 to 1.5 → midpoint (-1.5, 1.5, 0)
  - BR: x=1.5, y=-1.5, z from -1.5 to 1.5 → midpoint (1.5, -1.5, 0)
  - BL: x=-1.5, y=-1.5, z from -1.5 to 1.5 → midpoint (-1.5, -1.5, 0)

So the 12 edges:
1. UF: (0, 1.5, 1.5)
2. UR: (1.5, 0, 1.5)
3. UB: (0, 1.5, -1.5)
4. UL: (-1.5, 0, 1.5)
5. DF: (0, -1.5, 1.5)
6. DR: (1.5, -1.5, 1.5)
7. DB: (0, -1.5, -1.5)
8. DL: (-1.5, -1.5, 1.5)
9. FR: (1.5, 1.5, 0)
10. FL: (-1.5, 1.5, 0)
11. BR: (1.5, -1.5, 0)
12. BL: (-1.5, -1.5, 0)

Now, each edge cubie has two stickers. For example, UF edge has white (U) and green (F) stickers.

For the face centers (6 center pieces):
- U: (0, 1.5, 0) — white
- D: (0, -1.5, 0) — yellow
- F: (0, 0, 1.5) — green
- B: (0, 0, -1.5) — blue
- L: (-1.5, 0, 0) — orange
- R: (1.5, 0, 0) — red

And corner pieces (8):
- UFR: (1.5, 1.5, 1.5) — white, green, red
- UFL: (-1.5, 1.5, 1.5) — white, green, orange
- UBL: (-1.5, 1.5, -1.5) — white, blue, orange
- UBR: (1.5, 1.5, -1.5) — white, blue, red
- DFR: (1.5, -1.5, 1.5) — yellow, green, red
- DFL: (-1.5, -1.5, 1.5) — yellow, green, orange
- DBL: (-1.5, -1.5, -1.5) — yellow, blue, orange
- DBR: (1.5, -1.5, -1.5) — yellow, blue, red

Now, for the visual representation, I'll create the cube from these individual pieces. Each piece is a small box (size 1x1x1) positioned at its center. The box material will have colored faces.

For corner pieces, I need 3 colored faces. For edge pieces, 2 colored faces. For center pieces, 1 colored face.

Implementation plan:
- Create a scene with a perspective camera.
- Add a group for the cube (26 pieces).
- For each piece, create a Mesh with BoxGeometry(1, 1, 1) and a custom material that has multiple colors (or use MeshBasicMaterial with color, but that only gives one color per face).

Actually, BoxGeometry has 6 faces. To color different faces differently, I need either:
- Multiple materials per mesh (not possible directly)
- Custom shader material
- Or use a different approach: instead of colored boxes, use a single plane per face with stickers drawn on it.

Alternative approach: Use PlaneGeometry for each face, subdivided into 9 smaller rectangles. Each rectangle is a separate mesh with its own color. This way, each face shows a 3x3 grid of stickers. The cubie boundaries are the grid lines between stickers.

For example, the U face: a PlaneGeometry with width=3, height=3, subdivided into 3x3 segments. But PlaneGeometry doesn't support subdivision. However, I can create a custom geometry: a flat box with 9 rectangular faces arranged in a 3x3 grid.

Actually, the simplest approach that looks good: Use a BoxGeometry for the entire cube (3x3x3) and... no, that won't work for different colors.

Let me go with the individual cubie approach. For each cubie, I'll create a small box mesh. To color specific faces, I can use MeshBasicMaterial with a single color, but that colors all 6 faces. Instead, I can use MeshStandardMaterial with color, but again all faces get the same color.

Solution: Use a custom shader or use multiple materials. Actually, the easiest way is to use `MeshBasicMaterial` with `color` for the whole box, but then I can't have different colors per face.

Wait — I can use `MeshLambertMaterial` or just `MeshBasicMaterial`. For a Rubik's cube, the standard approach in simple WebGL demos is to create the cube from 6 face planes (each a PlaneGeometry with width=3, height=3, but subdivided into 9 smaller quads). Let me create a helper function that generates a face grid.

Actually, here's a clean approach: Create each face as a PlaneGeometry with width=3, height=3, and then manually split it into 9 smaller rectangles by creating 9 separate PlaneGeometry objects? No, that's not right either.

Let me think about what's really needed: "six distinct face colors and visible cubie boundaries". The key requirement is that the 6 faces have distinct colors and that the cubie boundaries (edges between cubies) are visible.

The simplest visual that satisfies this: Build the cube from 6 face materials (each a single color plane), and overlay a wireframe grid to show cubie boundaries. But the face colors need to be on the faces themselves.

Alternative: Use a BoxGeometry for the whole cube, but that's a solid cube. Not good.

OK, let me go with the individual cubie approach using small boxes. For coloring individual faces of a box, I can use `MeshBasicMaterial` with `color` — but this applies to all faces. However, I can create the box with `color` set to the primary color, and then... no, that won't work.

Actually, in Three.js, you can pass an array of colors to `MeshBasicMaterial`? No, that's not supported.

But wait — I can use `MeshStandardMaterial` with `side: THREE.DoubleSide` and `color`, but still all faces get the same color.

The trick: Create the box mesh, and then for each face, apply a different material? That's not possible with a single mesh.

Alternative: Don't use boxes. Use a different representation:
- 6 face planes, each a PlaneGeometry (width=3, height=3) with a color. These represent the face colors.
- Draw the cubie boundaries as lines (wireframe) on the faces.
- For the scramble/reset, track the state of each cubie position.

But the problem says "preserve cubie and sticker state through moves". If I use face planes, the stickers are just colors on planes, and I need to track which sticker is where.

Hmm, let me reconsider. Maybe the simplest robust approach is:

1. Create the cube from 26 small box meshes (cubies), each with a color based on its position.
2. For corner cubies: 3 colors. I can achieve this by creating the box with a material that has different colors on different faces... but Three.js BoxGeometry creates a box with all faces having the same material.

Actually, I can use `MeshBasicMaterial` with `color` for the whole box, but then paint the faces differently? No, that's not possible.

Wait, there's another approach: Use `THREE.Mesh` with `geometry: new THREE.BoxGeometry(1,1,1)` and `material: new THREE.MeshBasicMaterial({ color: ... })` — all 6 faces get the same color.

To get different colors on different faces of a single box, I'd need a custom shader or to use multiple meshes per cubie (e.g., 3 small boxes for a corner, but that's complex).

Given the constraints (no external libraries, must be self-contained), let me simplify: I'll represent the cube using 6 face planes (each a PlaneGeometry with width=3, height=3, subdivided into 9 smaller rectangles). Actually, PlaneGeometry can be subdivided if I use the `subdivisions` property! Yes! `PlaneGeometry(width, height, widthSegments, heightSegments)`. If I set widthSegments=3 and heightSegments=3, it creates a 3x3 grid of rectangles!

So for each face:
- U: PlaneGeometry(3, 3, 3, 3) — this creates 9 small rectangles, each 1x1.
- Color each rectangle according to its position on the face.

This is perfect! Each face becomes a 3x3 grid of colored quads. The cubie boundaries are the grid lines between them. And the face colors are distinct.

Let me verify: PlaneGeometry(width, height, widthSegments, heightSegments) creates a plane subdivided into (widthSegments x heightSegments) quads. With width=3, height=3, widthSegments=3, heightSegments=3, we get 9 rectangles of size 1x1.

So the plan:
- Scene: perspective camera, orbit controls (custom, since we can't use addons — but we can implement basic orbit controls ourselves).
- Background: maybe a dark gradient or simple color.
- 6 face planes, each with 9 colored rectangles.
- Orbit the camera by dragging (mouse events on the canvas).
- Zoom with wheel.

For the moves: U, D, L, R, F, B quarter turns. Each move rotates the appropriate face(s) of the cube. Since the cube is made of face planes, rotating a face means rotating the corresponding plane around its normal axis.

But wait — if I rotate a face plane, it will rotate in place, which is correct for a face turn. The other faces stay put. This is straightforward.

However, the cubie boundaries (grid lines) will rotate with the face, which is correct.

State tracking: I need to track the state of the cube (which sticker is where) so that moves are legal and the state is preserved. Since I'm using face planes with colored rectangles, the "state" is essentially the arrangement of colors on each face. When I rotate a face, I need to update the colors on that face accordingly.

Actually, for a proper Rubik's cube simulation, I should track the state of each sticker. But since I'm rendering with colored planes, I can just rotate the planes and update the colors.

Let me think about how to implement the moves:

Face definitions:
- U (up, white): rotates around Z axis (positive Z is up? In Three.js, Y is up. So U is +Y direction. Rotation around Y axis.)
- D (down, yellow): rotates around Y axis (negative direction for clockwise when looking from below)
- F (front, green): rotates around X axis
- B (back, blue): rotates around X axis
- L (left, orange): rotates around Z axis (negative)
- R (right, red): rotates around Z axis (positive)

Standard rotation directions (clockwise when looking at the face):
- U: +Y rotation (90°)
- D: -Y rotation (90°)
- F: +X rotation (90°)
- B: -X rotation (90°)
- R: +Z rotation (90°)
- L: -Z rotation (90°)

For each move, I need to rotate the face's plane and also update the adjacent faces' stickers that are affected by the move.

Since I'm representing each face as a 3x3 grid of quads, I can track the colors in a data structure and update them when a move happens.

Data structure: A 6x9 array (6 faces, 9 stickers per face). Each sticker has a color. The colors are: white, yellow, green, blue, orange, red.

Face order: Let's use indices 0-5:
0: U (white)
1: D (yellow)
2: F (green)
3: B (blue)
4: L (orange)
5: R (red)

Sticker positions on each face: 3x3 grid, indexed row-major (0-8):
- (0,0) top-left, (0,1) top-middle, (0,2) top-right
- (1,0) middle-left, etc.

When a face rotates, the stickers on that face cycle. For example, U face (rotating 90° clockwise):
- Sticker [0,0] → [0,2]
- [0,1] → [1,2]
- [0,2] → [2,2]
- [1,2] → [2,1]
- [2,2] → [2,0]
- [2,1] → [1,0]
- [2,0] → [0,0]
- [1,0] → [0,1]

Wait, let me be careful. A 90° clockwise rotation of a 3x3 grid:
- New[0][0] = old[0][2]? No.
- Clockwise: new[i][j] = old[n-1-j][i] for n=3.
- new[0][0] = old[2][0]
- new[0][1] = old[1][0]
- new[0][2] = old[0][0]
- new[1][0] = old[2][1]
- new[1][1] = old[1][1]
- new[1][2] = old[0][1]
- new[2][0] = old[2][2]
- new[2][1] = old[1][2]
- new[2][2] = old[0][2]

Let me verify: original:
1 2 3
4 5 6
7 8 9

After 90° clockwise:
7 4 1
8 5 2
9 6 3

So:
new[0][0]=7=old[2][0] ✓
new[0][1]=4=old[1][0