A hypnotic multi-colored spinning fan.
---
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"
}
}
```