---
format: typebulb/v1
name: Mobius Gears
---

**code.tsx**

```tsx
import React, { useMemo, useRef } from "react";
import { createRoot } from "react-dom/client";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { PerspectiveCamera } from "@react-three/drei";
import * as THREE from "three";

const TAU = Math.PI * 2;
const GEAR_COUNT = 19;      // Must be odd for proper Möbius interlocking
const TEETH_PER_GEAR = 11;

const Gear = ({ index, total, radius, gearRadius, teeth }: { index: number, total: number, radius: number, gearRadius: number, teeth: number }) => {
  const meshRef = useRef<THREE.Mesh>(null);

  const { shape, dir, position, quaternion, initialRotation, extrudeDepth } = useMemo(() => {
    const u = (index / total) * TAU;
    const dir = index % 2 === 0 ? 1 : -1;
    const depth = gearRadius * 0.27;
    const holeR = gearRadius * 0.23;
    const innerR = gearRadius - depth;
    const step = TAU / teeth;

    // Build Gear Shape
    const s = new THREE.Shape();
    for (let i = 0; i < teeth; i++) {
      const a = i * step;
      const x = innerR * Math.cos(a);
      const y = innerR * Math.sin(a);

      if (i === 0) s.moveTo(x, y);
      else s.lineTo(x, y);

      s.lineTo(innerR * Math.cos(a + step * 0.25), innerR * Math.sin(a + step * 0.25));
      s.lineTo(gearRadius * Math.cos(a + step * 0.3), gearRadius * Math.sin(a + step * 0.3));
      s.lineTo(gearRadius * Math.cos(a + step * 0.7), gearRadius * Math.sin(a + step * 0.7));
      s.lineTo(innerR * Math.cos(a + step * 0.75), innerR * Math.sin(a + step * 0.75));
    }
    s.closePath();
    s.holes.push(new THREE.Path().absarc(0, 0, holeR, 0, TAU, false));

    // Calculate Mobius Position & Orientation
    const pos = new THREE.Vector3(radius * Math.cos(u), radius * Math.sin(u), 0);
    const T = new THREE.Vector3(-Math.sin(u), Math.cos(u), 0);
    const hu = u / 2;
    const W = new THREE.Vector3(Math.cos(hu) * Math.cos(u), Math.cos(hu) * Math.sin(u), Math.sin(hu)).normalize();
    const nVec = new THREE.Vector3().crossVectors(T, W).normalize();
    const quat = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(T, W, nVec));

    // Tooth phasing must NOT drift continuously with u, otherwise some adjacent
    // pairs will end up tooth-on-tooth (overlap).
    //
    // With an odd tooth count, opposite contact points (+x vs -x) naturally
    // produce the needed half-pitch relationship, so keep all gears on the
    // same phase.
    //
    // With an even tooth count, offset every other gear by half a tooth pitch.
    const meshingOffset = (teeth % 2 === 0 && dir < 0) ? step / 2 : 0;

    return {
      shape: s,
      dir,
      position: pos,
      quaternion: quat,
      initialRotation: meshingOffset,
      extrudeDepth: gearRadius * 0.17
    };
  }, [index, total, radius, gearRadius, teeth]);

  useFrame((state, delta) => {
    if (meshRef.current) {
      meshRef.current.rotation.z += delta * 1.5 * dir;
    }
  });

  return (
    <group position={position} quaternion={quaternion}>
      {/* ExtrudeGeometry extrudes in +Z from 0..depth (not centered).
          Center it around the local origin so neighboring gears share the same mid-plane,
          avoiding a subtle "kink" where two meshing gears look axially offset. */}
      <mesh ref={meshRef} rotation={[0, 0, initialRotation]} position={[0, 0, -extrudeDepth / 2]}>
        <extrudeGeometry args={[shape, { depth: extrudeDepth, bevelEnabled: false }]} />
        <meshStandardMaterial 
          color={new THREE.Color().setHSL(index / total, 1, 0.5)} 
          metalness={0.8} 
          roughness={0.2} 
        />
      </mesh>
    </group>
  );
};

const Scene = ({ radius, verticalOffset = 0 }: { radius: number, verticalOffset?: number }) => {
  const gearRadius = radius * Math.sin(Math.PI / GEAR_COUNT) * 1.15;
  const groupRef = useRef<THREE.Group>(null);
  const { viewport } = useThree();

  const tilt = Math.PI / 3;

  const responsiveScale = useMemo(() => {
    const outerR = radius + gearRadius;

    // Width is determined by the horizontal diameter of the ring
    const projectedW = outerR * 2;

    // Height is determined by the tilted ring diameter PLUS the gear thickness 
    // which can stand 'upright' due to the Mobius twist.
    const projectedH = (radius * 2 * Math.cos(tilt)) + (gearRadius * 2);

    const scaleX = viewport.width / projectedW;
    const scaleY = viewport.height / projectedH;

    // Increase multiplier to 0.85 to fill more space while keeping a safe margin
    return Math.min(scaleX, scaleY) * 0.85;
  }, [viewport.width, viewport.height, radius, gearRadius, tilt]);

  useFrame((state, delta) => {
    if (groupRef.current) groupRef.current.rotation.z += delta * 0.2;
  });

  return (
    <group position={[0, verticalOffset * viewport.height, 0]} rotation={[-tilt, 0, 0]} scale={responsiveScale}>
      <group ref={groupRef}>
        {Array.from({ length: GEAR_COUNT }).map((_, i) => (
          <Gear key={i} index={i} total={GEAR_COUNT} radius={radius} gearRadius={gearRadius} teeth={TEETH_PER_GEAR} />
        ))}
      </group>
    </group>
  );
};

const App = () => {
  return (
    <>
    <Canvas dpr={[1, 2]} gl={{ antialias: true, alpha: true }}>
      {/* High distance and low FOV approximates an orthographic camera, 
          eliminating perspective centering issues where the front of the ring 
          looks larger and lower than the back. */}
      <PerspectiveCamera makeDefault position={[0, 0, 50]} fov={5.5} />
      <ambientLight intensity={2.5} color="#ffffff" />
      <directionalLight position={[4, 6, 5]} intensity={3} color="#fff0dd" />
      <directionalLight position={[-3, -2, -3]} intensity={1.2} color="#6688cc" />
      <Scene radius={1} verticalOffset={0.05} />
    </Canvas>
    <div className="description">
      Gears on a Möbius strip create an illusion where because the entire strip is rotating, alternating gears seem to be turning faster/slower. Concept courtesy of @prof_g
    </div>
    </>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(<App />);
```
**styles.css**

```css
html, body, #root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: #000000;
}
.description {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 32px);
  max-width: 600px;
  text-align: center;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 16px;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.7);
  pointer-events: none;
  z-index: 100;
  text-wrap: balance;
}

```
**index.html**

```html
<div id="root"></div>

```
**data.txt**

```txt


```
**infer.md**

```md



```
**insight.json**

```json



```
**config.json**

```json
{
  "description": "Gears on a mobius strip create an illusion where because the entire strip is rotating, alternating gears seem to be turning faster/slower.",
  "dependencies": {
    "three": "^0.165.0",
    "react": "^19.2.4",
    "react-dom": "^19.2.4",
    "@react-three/fiber": "^9.5.0",
    "@react-three/drei": "^10.7.7"
  }
}
```