---
format: typebulb/v1
name: Spinning Fan
---

**code.tsx**

```tsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";

function Fan({ paused }: { paused: boolean }) {
  return (
    <svg
      className={`fan ${paused ? "paused" : ""}`}
      viewBox="-130 -130 260 260"
    >
      <circle cx={0} cy={0} r={120} fill="#ED1C24" />
      <circle cx={0} cy={15} r={105} fill="#3F48CC" />
      <circle cx={0} cy={-20} r={70} fill="#00FF00" />
      <circle cx={0} cy={-20} r={35} fill="#ED1C24" />
    </svg>
  );
}

function App() {
  const [paused, setPaused] = useState(false);
  const [speed, setSpeed] = useState(0.3);

  return (
    <div
      className="stage"
      style={{ ["--spin-duration" as never]: `${speed}s` }}
      onClick={() => setPaused((p) => !p)}
      title={paused ? "Click to spin" : "Click to pause"}
    >
      <Fan paused={paused} />

      <div className="controls" onClick={(e) => e.stopPropagation()}>
        <label>
          Speed
          <input
            type="range"
            min={0.02}
            max={2}
            step={0.02}
            value={2.02 - speed}
            onChange={(e) => setSpeed(2.02 - parseFloat(e.target.value))}
          />
        </label>
        <button className="btn" onClick={() => setPaused((p) => !p)}>
          {paused ? "Spin" : "Pause"}
        </button>
      </div>
    </div>
  );
}

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

```css
:root {
  --bg: #f5f5f7;
  --bg2: #ffffff;
  --fg: #1d1d1f;
  --fg2: #6e6e73;
  --accent: #6366f1;
  --border: #d2d2d7;
  --spin-duration: 0.3s;
}

html[data-theme="dark"] {
  --bg: #09090b;
  --bg2: #18181b;
  --fg: #f4f4f5;
  --fg2: #a1a1aa;
  --accent: #818cf8;
  --border: #3f3f46;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--fg);
}

.stage {
  position: fixed;
  inset: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  user-select: none;
}

.fan {
  width: min(100vw, 100vh);
  height: min(100vw, 100vh);
  display: block;
  animation: spin var(--spin-duration) linear infinite;
  filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.25));
}

.fan.paused { animation-play-state: paused; }

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.controls {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  background: color-mix(in srgb, var(--bg2) 85%, transparent);
  border: 1px solid var(--border);
  border-radius: 12px;
  backdrop-filter: blur(8px);
  cursor: default;
  z-index: 10;
}

.controls label {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg2);
}

.controls input[type="range"] {
  width: 200px;
  accent-color: var(--accent);
}

.btn {
  background: var(--accent);
  color: white;
  border: none;
  padding: 0.55rem 1.4rem;
  font-size: 0.9rem;
  font-weight: 700;
  border-radius: 50px;
  cursor: pointer;
  transition: transform 0.2s;
}

.btn:hover { transform: scale(1.05); }

@media (max-width: 600px) {
  .controls {
    bottom: 0.75rem;
    right: 0.75rem;
    gap: 0.6rem;
    padding: 0.5rem 0.75rem;
  }
  .controls input[type="range"] { width: 120px; }
}
```
**index.html**

```html
<div id="root"></div>
```
**config.json**

```json
{
  "description": "A hypnotic multi-colored spinning fan.",
  "dependencies": {
    "react": "^19.2.3",
    "react-dom": "^19.2.3"
  }
}
```