Skip to content

Instantly share code, notes, and snippets.

@CapsAdmin
Created August 17, 2026 06:43
Show Gist options
  • Select an option

  • Save CapsAdmin/b0ea64006f942c5a96a56dba78117f79 to your computer and use it in GitHub Desktop.

Select an option

Save CapsAdmin/b0ea64006f942c5a96a56dba78117f79 to your computer and use it in GitHub Desktop.
testing qwen 3.8 with thinking cap lora
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fancy Circle</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #0a0a1a;
overflow: hidden;
}
.container {
position: relative;
width: 400px;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
}
/* Central circle */
.circle {
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #667eea, #764ba2, #f093fb);
position: relative;
z-index: 10;
animation: pulse 3s ease-in-out infinite;
box-shadow:
0 0 40px rgba(118, 75, 162, 0.6),
0 0 80px rgba(102, 126, 234, 0.3),
inset 0 0 30px rgba(255, 255, 255, 0.1);
}
.circle::before {
content: '';
position: absolute;
top: 10%;
left: 15%;
width: 40%;
height: 30%;
background: radial-gradient(ellipse, rgba(255,255,255,0.5), transparent);
border-radius: 50%;
filter: blur(4px);
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
/* Rotating ring */
.ring {
position: absolute;
width: 260px;
height: 260px;
border-radius: 50%;
border: 2px solid transparent;
background: conic-gradient(from 0deg, #667eea, #f093fb, #667eea) border-box;
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation: spin 4s linear infinite;
}
.ring-2 {
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
border: 1px solid rgba(240, 147, 251, 0.3);
animation: spin-reverse 6s linear infinite;
}
.ring-2::before {
content: '';
position: absolute;
top: -4px;
left: 50%;
width: 8px;
height: 8px;
background: #f093fb;
border-radius: 50%;
box-shadow: 0 0 10px #f093fb;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes spin-reverse {
to { transform: rotate(-360deg); }
}
/* Orbiting dots */
.orbit {
position: absolute;
width: 280px;
height: 280px;
animation: spin 5s linear infinite;
}
.orbit .dot {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background: #667eea;
box-shadow: 0 0 12px #667eea;
top: 50%;
left: -6px;
transform: translateY(-50%);
}
.orbit-2 {
position: absolute;
width: 340px;
height: 340px;
animation: spin-reverse 8s linear infinite;
}
.orbit-2 .dot {
background: #f093fb;
box-shadow: 0 0 12px #f093fb;
width: 8px;
height: 8px;
}
.orbit-3 {
position: absolute;
width: 380px;
height: 380px;
animation: spin 12s linear infinite;
}
.orbit-3 .dot {
background: #a78bfa;
box-shadow: 0 0 12px #a78bfa;
width: 6px;
height: 6px;
}
/* Glow ring */
.glow-ring {
position: absolute;
width: 220px;
height: 220px;
border-radius: 50%;
border: 1px solid rgba(102, 126, 234, 0.2);
animation: glow-pulse 3s ease-in-out infinite;
}
@keyframes glow-pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.3); opacity: 0; }
}
.glow-ring:nth-child(2) {
animation-delay: 1s;
}
.glow-ring:nth-child(3) {
animation-delay: 2s;
}
/* Sparkle particles */
.sparkle {
position: absolute;
width: 3px;
height: 3px;
background: white;
border-radius: 50%;
animation: twinkle 2s ease-in-out infinite;
}
.sparkle:nth-child(1) { top: 20%; left: 30%; animation-delay: 0s; }
.sparkle:nth-child(2) { top: 60%; left: 70%; animation-delay: 0.5s; }
.sparkle:nth-child(3) { top: 40%; left: 85%; animation-delay: 1s; }
.sparkle:nth-child(4) { top: 80%; left: 25%; animation-delay: 1.5s; }
.sparkle:nth-child(5) { top: 15%; left: 65%; animation-delay: 0.3s; }
.sparkle:nth-child(6) { top: 70%; left: 45%; animation-delay: 0.8s; }
@keyframes twinkle {
0%, 100% { opacity: 0; transform: scale(0); }
50% { opacity: 1; transform: scale(1); }
}
</style>
</head>
<body>
<div class="container">
<div class="glow-ring"></div>
<div class="glow-ring"></div>
<div class="glow-ring"></div>
<div class="ring"></div>
<div class="ring-2"></div>
<div class="orbit"><div class="dot"></div></div>
<div class="orbit orbit-2"><div class="dot"></div></div>
<div class="orbit orbit-3"><div class="dot"></div></div>
<div class="circle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
</div>
</body>
</html>
<script type="text/plain" id="launch-params">
llama-cli --prompt create a fancy circle in html --seed 1337 --single-turn --output output.txt --flash-attn on --load-mode dio --fit on --parallel 1 --threads 32 --fit-target 0 --n-gpu-layers all --cpu-strict 1 --log-verbosity 3 --batch-size 2048 --ubatch-size 512 --spec-type draft-mtp --spec-draft-n-max 5 --cache-type-k-draft q4_0 --cache-type-v-draft q4_0 --model /run/media/caps/extra/llm-models/Qwen3.8-27B-UD-Q4_K_XL.gguf --cache-type-k q8_0 --cache-type-v q8_0 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --temperature 1.0 --top-p 0.95 --presence-penalty 0.0 --reasoning on --reasoning-effort low
</script><script type="text/plain" id="chat">
User:
create a fancy circle in html
Assistant:
[Start thinking]
The user wants a fancy circle in HTML. Let me think about what would make a circle "fancy":
- Gradient colors
- Animation (pulsing, rotating, glowing)
- Multiple layers
- Shadow/glow effects
- Maybe some CSS-only animations
I'll create a visually impressive circle with multiple animated layers, gradients, glow effects, and maybe some orbiting elements. Let me make it self-contained in a single HTML file with embedded CSS.
I'll go with:
- A central circle with a conic/radial gradient
- A glowing pulse animation
- Orbiting smaller circles
- A rotating gradient border
- Maybe some particle-like dots
- Dark background to make it pop
Let me craft this carefully.
[End thinking]
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fancy Circle</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #0a0a1a;
overflow: hidden;
}
.container {
position: relative;
width: 400px;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
}
/* Central circle */
.circle {
width: 180px;
height: 180px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #667eea, #764ba2, #f093fb);
position: relative;
z-index: 10;
animation: pulse 3s ease-in-out infinite;
box-shadow:
0 0 40px rgba(118, 75, 162, 0.6),
0 0 80px rgba(102, 126, 234, 0.3),
inset 0 0 30px rgba(255, 255, 255, 0.1);
}
.circle::before {
content: '';
position: absolute;
top: 10%;
left: 15%;
width: 40%;
height: 30%;
background: radial-gradient(ellipse, rgba(255,255,255,0.5), transparent);
border-radius: 50%;
filter: blur(4px);
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
/* Rotating ring */
.ring {
position: absolute;
width: 260px;
height: 260px;
border-radius: 50%;
border: 2px solid transparent;
background: conic-gradient(from 0deg, #667eea, #f093fb, #667eea) border-box;
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
animation: spin 4s linear infinite;
}
.ring-2 {
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
border: 1px solid rgba(240, 147, 251, 0.3);
animation: spin-reverse 6s linear infinite;
}
.ring-2::before {
content: '';
position: absolute;
top: -4px;
left: 50%;
width: 8px;
height: 8px;
background: #f093fb;
border-radius: 50%;
box-shadow: 0 0 10px #f093fb;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes spin-reverse {
to { transform: rotate(-360deg); }
}
/* Orbiting dots */
.orbit {
position: absolute;
width: 280px;
height: 280px;
animation: spin 5s linear infinite;
}
.orbit .dot {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
background: #667eea;
box-shadow: 0 0 12px #667eea;
top: 50%;
left: -6px;
transform: translateY(-50%);
}
.orbit-2 {
position: absolute;
width: 340px;
height: 340px;
animation: spin-reverse 8s linear infinite;
}
.orbit-2 .dot {
background: #f093fb;
box-shadow: 0 0 12px #f093fb;
width: 8px;
height: 8px;
}
.orbit-3 {
position: absolute;
width: 380px;
height: 380px;
animation: spin 12s linear infinite;
}
.orbit-3 .dot {
background: #a78bfa;
box-shadow: 0 0 12px #a78bfa;
width: 6px;
height: 6px;
}
/* Glow ring */
.glow-ring {
position: absolute;
width: 220px;
height: 220px;
border-radius: 50%;
border: 1px solid rgba(102, 126, 234, 0.2);
animation: glow-pulse 3s ease-in-out infinite;
}
@keyframes glow-pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.3); opacity: 0; }
}
.glow-ring:nth-child(2) {
animation-delay: 1s;
}
.glow-ring:nth-child(3) {
animation-delay: 2s;
}
/* Sparkle particles */
.sparkle {
position: absolute;
width: 3px;
height: 3px;
background: white;
border-radius: 50%;
animation: twinkle 2s ease-in-out infinite;
}
.sparkle:nth-child(1) { top: 20%; left: 30%; animation-delay: 0s; }
.sparkle:nth-child(2) { top: 60%; left: 70%; animation-delay: 0.5s; }
.sparkle:nth-child(3) { top: 40%; left: 85%; animation-delay: 1s; }
.sparkle:nth-child(4) { top: 80%; left: 25%; animation-delay: 1.5s; }
.sparkle:nth-child(5) { top: 15%; left: 65%; animation-delay: 0.3s; }
.sparkle:nth-child(6) { top: 70%; left: 45%; animation-delay: 0.8s; }
@keyframes twinkle {
0%, 100% { opacity: 0; transform: scale(0); }
50% { opacity: 1; transform: scale(1); }
}
</style>
</head>
<body>
<div class="container">
<div class="glow-ring"></div>
<div class="glow-ring"></div>
<div class="glow-ring"></div>
<div class="ring"></div>
<div class="ring-2"></div>
<div class="orbit"><div class="dot"></div></div>
<div class="orbit orbit-2"><div class="dot"></div></div>
<div class="orbit orbit-3"><div class="dot"></div></div>
<div class="circle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
<div class="sparkle"></div>
</div>
</body>
</html>
```
## What you get:
| Feature | Effect |
|---------|--------|
| **Central circle** | Radial gradient with a glossy highlight, gently pulsing |
| **Conic gradient ring** | Spinning rainbow-colored border |
| **Counter-rotating ring** | Thin ring with a glowing dot orbiting the other way |
| **3 orbiting dots** | Different sizes, speeds, and directions |
| **Glow rings** | Expanding/fading pulse waves |
| **Sparkles** | Tiny twinkling particles scattered around |
| **Dark background** | Makes all the glow effects pop |
Just save it as an `.html` file and open it in a browser — no dependencies needed. ✨
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fancy Circle</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, #1a1a2e 0%, #0d0d16 100%);
overflow: hidden;
}
.circle-container {
position: relative;
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
/* Main glowing circle */
.circle {
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ff6ec4, #7873f5 60%, #48c6ef 100%);
box-shadow:
0 0 40px rgba(120, 115, 245, 0.6),
0 0 80px rgba(255, 110, 196, 0.4),
inset 0 0 40px rgba(255, 255, 255, 0.2);
animation: pulse 3s ease-in-out infinite;
}
/* Shiny highlight */
.circle::before {
content: '';
position: absolute;
top: 35px;
left: 55px;
width: 60px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
filter: blur(10px);
transform: rotate(-25deg);
}
/* Rotating ring 1 */
.ring-1 {
position: absolute;
width: 250px;
height: 250px;
border-radius: 50%;
border: 2px dashed rgba(72, 198, 239, 0.5);
animation: spin 12s linear infinite;
}
/* Rotating ring 2 (opposite direction) */
.ring-2 {
position: absolute;
width: 280px;
height: 280px;
border-radius: 50%;
border: 2px dotted rgba(255, 110, 196, 0.5);
animation: spin-reverse 20s linear infinite;
}
/* Orbiting dot */
.orbit {
position: absolute;
width: 250px;
height: 250px;
animation: spin 6s linear infinite;
}
.orbit .dot {
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 15px #fff, 0 0 30px #48c6ef;
}
/* Animations */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes spin-reverse {
to { transform: rotate(-360deg); }
}
</style>
</head>
<body>
<div class="circle-container">
<div class="ring-2"></div>
<div class="ring-1"></div>
<div class="orbit"><div class="dot"></div></div>
<div class="circle"></div>
</div>
</body>
</html>
<script type="text/plain" id="launch-params">
llama-cli --prompt create a fancy circle in html --seed 1337 --single-turn --output output.txt --flash-attn on --load-mode dio --fit on --parallel 1 --threads 32 --fit-target 0 --n-gpu-layers all --cpu-strict 1 --log-verbosity 3 --batch-size 2048 --ubatch-size 512 --spec-type draft-mtp --spec-draft-n-max 5 --cache-type-k-draft q4_0 --cache-type-v-draft q4_0 --model /run/media/caps/extra/llm-models/Qwen3.8-27B-UD-Q4_K_XL.gguf --cache-type-k q8_0 --cache-type-v q8_0 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --temperature 1.0 --top-p 0.95 --presence-penalty 0.0 --reasoning on --reasoning-effort medium
</script><script type="text/plain" id="chat">
User:
create a fancy circle in html
Assistant:
[Start thinking]
The user is asking for a "fancy circle" in HTML. Let's create a visually appealing HTML/CSS circle — with gradient, animation, and a glow effect. Provide a complete HTML file.
[End thinking]
# Fancy Animated Circle
Here's a beautiful animated circle with gradients, glow effects, and rotating rings:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fancy Circle</title>
<style>
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(ellipse at center, #1a1a2e 0%, #0d0d16 100%);
overflow: hidden;
}
.circle-container {
position: relative;
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
/* Main glowing circle */
.circle {
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ff6ec4, #7873f5 60%, #48c6ef 100%);
box-shadow:
0 0 40px rgba(120, 115, 245, 0.6),
0 0 80px rgba(255, 110, 196, 0.4),
inset 0 0 40px rgba(255, 255, 255, 0.2);
animation: pulse 3s ease-in-out infinite;
}
/* Shiny highlight */
.circle::before {
content: '';
position: absolute;
top: 35px;
left: 55px;
width: 60px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
filter: blur(10px);
transform: rotate(-25deg);
}
/* Rotating ring 1 */
.ring-1 {
position: absolute;
width: 250px;
height: 250px;
border-radius: 50%;
border: 2px dashed rgba(72, 198, 239, 0.5);
animation: spin 12s linear infinite;
}
/* Rotating ring 2 (opposite direction) */
.ring-2 {
position: absolute;
width: 280px;
height: 280px;
border-radius: 50%;
border: 2px dotted rgba(255, 110, 196, 0.5);
animation: spin-reverse 20s linear infinite;
}
/* Orbiting dot */
.orbit {
position: absolute;
width: 250px;
height: 250px;
animation: spin 6s linear infinite;
}
.orbit .dot {
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 15px #fff, 0 0 30px #48c6ef;
}
/* Animations */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes spin-reverse {
to { transform: rotate(-360deg); }
}
</style>
</head>
<body>
<div class="circle-container">
<div class="ring-2"></div>
<div class="ring-1"></div>
<div class="orbit"><div class="dot"></div></div>
<div class="circle"></div>
</div>
</body>
</html>
```
## Features ✨
- **Gradient sphere** — purple/pink/cyan with a soft inner glow and a shiny highlight, giving it a 3D look
- **Pulsing animation** — the circle gently "breathes"
- **Two rotating rings** — one dashed, one dotted, spinning in opposite directions
- **Orbiting glowing dot** — circles around the main sphere
- **Dark space background** with a radial gradient
Just save it as `circle.html` and open it in any browser. Want me to tweak the colors, size, or add more effects (particles, hover interactions, etc.)?
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Kinetic Circle — Specimen Nº 001</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Unbounded:wght@400;700;900&display=swap"
rel="stylesheet">
<style>
:root {
--hue: 0;
--bg: #0a141b;
--ink: #e4f1f5;
--mut: #6f8d99;
--gold: hsl(calc(40 + var(--hue)) 95% 66%);
--cyan: hsl(calc(172 + var(--hue)) 75% 62%);
--coral: hsl(calc(8 + var(--hue)) 90% 64%);
--line: hsl(calc(190 + var(--hue)) 35% 50% / .22);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
background:
radial-gradient(1100px 760px at 50% 42%, hsl(calc(172 + var(--hue)) 45% 13% / .6), transparent 62%),
radial-gradient(900px 700px at 80% 86%, hsl(calc(40 + var(--hue)) 50% 12% / .45), transparent 60%),
var(--bg);
color: var(--ink);
font-family: 'Space Mono', monospace;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
::selection {
background: var(--gold);
color: var(--bg);
}
canvas#sky {
position: fixed;
inset: 0;
z-index: 0;
}
body::before {
/* vignette */
content: "";
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
background: radial-gradient(125% 125% at 50% 44%, transparent 52%, rgba(3, 9, 13, .8) 100%);
}
body::after {
/* grain */
content: "";
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
opacity: .045;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
.frame {
position: fixed;
inset: 0;
z-index: 2;
display: grid;
grid-template-rows: auto 1fr auto;
padding: clamp(18px, 3.4vw, 44px);
gap: 10px;
pointer-events: none;
}
.frame * {
pointer-events: none;
}
.panel,
.hud,
.btn,
input {
pointer-events: auto;
}
/* ── header ─────────────────────────────── */
.head {
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: 28px;
}
.kicker {
font-size: 10px;
letter-spacing: .32em;
color: var(--gold);
text-transform: uppercase;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.kicker::before {
content: "";
width: 7px;
height: 7px;
background: var(--gold);
flex: none;
}
.title .line1 {
display: block;
font-family: 'Unbounded', sans-serif;
font-weight: 700;
font-size: clamp(.72rem, 1.5vw, 1rem);
letter-spacing: .46em;
text-transform: uppercase;
color: var(--mut);
margin-bottom: 4px;
}
.title .line2 {
display: block;
font-family: 'Unbounded', sans-serif;
font-weight: 900;
font-size: clamp(2.5rem, 7vw, 4.6rem);
line-height: 1;
letter-spacing: .01em;
color: var(--ink);
}
.title .line2::after {
content: ".";
color: var(--gold);
}
.head-right {
max-width: 36ch;
text-align: right;
font-size: 12px;
line-height: 1.75;
color: var(--mut);
}
.hint {
margin-top: 10px;
font-size: 11px;
letter-spacing: .08em;
}
kbd {
font-family: inherit;
font-size: 10px;
padding: 2px 7px;
border: 1px solid var(--line);
border-radius: 3px;
color: var(--ink);
}
/* ── stage & orbit ──────────────────────── */
.stage {
position: relative;
display: grid;
place-items: center;
cursor: crosshair;
}
.cross {
position: absolute;
background: linear-gradient(90deg, transparent, var(--line) 18% 82%, transparent);
}
.cross-h {
left: 0;
right: 0;
top: 50%;
height: 1px;
}
.cross-v {
top: 0;
bottom: 0;
left: 50%;
width: 1px;
background: linear-gradient(180deg, transparent, var(--line) 18% 82%, transparent);
}
.orbit {
position: relative;
width: min(66vmin, 620px);
min-width: 280px;
aspect-ratio: 1;
}
.layer {
position: absolute;
inset: 0;
will-change: transform;
}
.sweep {
position: absolute;
inset: -10%;
border-radius: 50%;
will-change: transform;
background: conic-gradient(from 0deg,
transparent 0deg 300deg,
hsl(calc(40 + var(--hue)) 95% 66% / 0) 318deg,
hsl(calc(40 + var(--hue)) 95% 66% / .13) 350deg,
transparent 360deg);
}
.ticks,
.text-ring {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
will-change: transform;
}
.ring-outer {
position: absolute;
inset: 0;
border: 1px solid var(--line);
border-radius: 50%;
will-change: transform;
}
.comet {
position: absolute;
top: 0;
left: 50%;
width: 7px;
height: 7px;
border-radius: 50%;
transform: translate(-50%, -50%);
background: var(--gold);
box-shadow: 0 0 12px 3px hsl(calc(40 + var(--hue)) 95% 66% / .8);
}
.orb {
position: absolute;
border-radius: 50%;
will-change: transform;
}
.orb i {
position: absolute;
top: 0;
left: 50%;
border-radius: 50%;
transform: translate(-50%, -50%);
width: 7px;
height: 7px;
background: currentColor;
box-shadow: 0 0 12px 2px currentColor;
}
.orb-a {
inset: -7%;
color: var(--gold);
}
.orb-b {
inset: 12%;
color: var(--cyan);
}
.orb-b i {
width: 5px;
height: 5px;
}
.orb-c {
inset: -16%;
color: var(--coral);
}
.orb-c i {
width: 4px;
height: 4px;
}
/* the core */
.core {
position: absolute;
inset: 0;
margin: auto;
width: 30%;
height: 30%;
border: none;
border-radius: 50%;
cursor: pointer;
appearance: none;
background: radial-gradient(circle at 35% 32%, #fff6e6 0%, var(--gold) 32%, var(--coral) 68%, hsl(calc(8 + var(--hue)) 65% 24%) 100%);
box-shadow:
0 0 44px hsl(calc(40 + var(--hue)) 95% 60% / .5),
0 0 130px hsl(calc(40 + var(--hue)) 95% 60% / .22);
transition: filter .45s ease;
animation: breathe 4.6s ease-in-out infinite;
}
.core:focus-visible {
outline: 2px dashed var(--cyan);
outline-offset: 8px;
}
.core.flare {
filter: brightness(1.55) saturate(1.35);
}
.core-glow {
position: absolute;
inset: -38%;
border-radius: 50%;
background: radial-gradient(circle, hsl(calc(40 + var(--hue)) 95% 66% / .5), hsl(calc(40 + var(--hue)) 95% 66% / .1) 46%, transparent 72%);
animation: glowPulse 4.6s ease-in-out infinite;
transition: transform .5s ease;
}
.core.flare .core-glow {
transform: scale(1.3);
}
.core-surface {
position: absolute;
inset: 10%;
border-radius: 50%;
opacity: .45;
mix-blend-mode: screen;
background: conic-gradient(from 0deg,
transparent 0deg 40deg, hsl(calc(40 + var(--hue)) 95% 75% / .5) 100deg,
transparent 170deg, hsl(calc(8 + var(--hue)) 90% 70% / .4) 260deg, transparent 320deg);
animation: spin 18s linear infinite;
}
.core-ring {
position: absolute;
inset: 5.5%;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, .35);
}
.core::after {
content: "";
position: absolute;
top: 15%;
left: 22%;
width: 27%;
height: 27%;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 255, 255, .95), transparent 70%);
filter: blur(2px);
}
@keyframes breathe {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
@keyframes glowPulse {
0%,
100% {
opacity: .7;
}
50% {
opacity: 1;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ── bottom row: panel + hud ────────────── */
.row3 {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 24px;
}
.panel {
width: 242px;
padding: 16px 18px 18px;
position: relative;
background: hsl(calc(200 + var(--hue)) 35% 8% / .72);
border: 1px solid var(--line);
}
.panel::before {
content: "";
position: absolute;
top: -1px;
left: -1px;
width: 11px;
height: 11px;
border-top: 2px solid var(--gold);
border-left: 2px solid var(--gold);
}
.panel-title {
font-family: 'Unbounded', sans-serif;
font-weight: 700;
font-size: 10px;
letter-spacing: .26em;
color: var(--mut);
margin-bottom: 16px;
}
.ctl {
display: block;
margin-bottom: 16px;
}
.ctl-label {
display: flex;
justify-content: space-between;
font-size: 10px;
letter-spacing: .18em;
color: var(--mut);
margin-bottom: 8px;
}
.ctl-label output {
color: var(--gold);
letter-spacing: .05em;
}
input[type=range] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 2px;
display: block;
cursor: ew-resize;
background: linear-gradient(90deg, var(--gold) var(--fill, 33%), hsl(calc(190 + var(--hue)) 25% 45% / .3) var(--fill, 33%));
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--bg);
border: 2px solid var(--gold);
transition: background .2s;
}
input[type=range]::-webkit-slider-thumb:hover {
background: var(--gold);
}
input[type=range]::-moz-range-thumb {
width: 11px;
height: 11px;
border-radius: 50%;
background: var(--bg);
border: 2px solid var(--gold);
}
input[type=range]:focus-visible {
outline: 2px dashed var(--cyan);
outline-offset: 6px;
}
.btns {
display: flex;
gap: 8px;
}
.btn {
font-family: 'Space Mono', monospace;
font-size: 11px;
letter-spacing: .1em;
padding: 9px 13px;
background: transparent;
color: var(--ink);
border: 1px solid var(--line);
cursor: pointer;
transition: border-color .2s, color .2s, background .2s, transform .1s;
}
.btn:hover {
border-color: var(--gold);
color: var(--gold);
}
.btn:active {
transform: translateY(1px);
}
.btn[aria-pressed="true"] {
background: var(--gold);
border-color: var(--gold);
color: var(--bg);
}
.btn:focus-visible {
outline: 2px dashed var(--cyan);
outline-offset: 3px;
}
.hud {
display: flex;
gap: 26px;
font-size: 11px;
letter-spacing: .12em;
color: var(--mut);
}
.hud span {
display: flex;
gap: 9px;
align-items: baseline;
white-space: nowrap;
}
.hud b {
font-weight: 400;
color: var(--ink);
}
#hudState {
color: var(--gold);
animation: blink 2.2s ease-in-out infinite;
}
#hudState.held {
color: var(--coral);
animation: none;
}
@keyframes blink {
50% {
opacity: .45;
}
}
@media (max-width: 760px) {
.head {
grid-template-columns: 1fr;
gap: 14px;
}
.head-right {
text-align: left;
max-width: none;
}
.orbit {
width: min(80vmin, 480px);
}
.row3 {
flex-direction: column;
align-items: stretch;
gap: 14px;
}
.panel {
width: auto;
}
.hud {
justify-content: flex-start;
flex-wrap: wrap;
gap: 14px 22px;
}
}
@media (prefers-reduced-motion: reduce) {
.core,
.core-glow,
.core-surface,
#hudState {
animation: none;
}
}
</style>
</head>
<body>
<canvas id="sky"></canvas>
<div class="frame">
<header class="head">
<div>
<p class="kicker">Specimen Nº 001 · Circular Form</p>
<h1 class="title">
<span class="line1">The Kinetic</span>
<span class="line2">Circle</span>
</h1>
</div>
<div class="head-right">
<p>A living study in orbital motion — five rings turning at their own tempos, a core that breathes, and a
starfield that answers back. Steer the dials, drift the cursor, fire a pulse.</p>
<p class="hint"><kbd>click</kbd> / <kbd>space</kbd> &nbsp;→&nbsp; fire a pulse</p>
</div>
</header>
<div class="stage" id="stage">
<div class="cross cross-h"></div>
<div class="cross cross-v"></div>
<div class="orbit" id="orbit">
<div class="layer" data-depth="8">
<div class="sweep" id="sweep"></div>
</div>
<div class="layer" data-depth="10">
<svg class="ticks" id="ticks" viewBox="0 0 200 200" aria-hidden="true">
<circle cx="100" cy="100" r="99" fill="none" pathLength="360" stroke="var(--line)" stroke-width="4.5"
stroke-dasharray="0.6 5.4" opacity=".8" />
<circle cx="100" cy="100" r="91" fill="none" pathLength="360"
stroke="hsl(calc(172 + var(--hue)) 75% 62% / .35)" stroke-width="10" stroke-dasharray="1.2 38.8" />
</svg>
</div>
<div class="layer" data-depth="16">
<svg class="text-ring" id="textRing" viewBox="0 0 200 200" aria-hidden="true">
<defs>
<path id="tp" d="M100,100 m-78,0 a78,78 0 1,1 156,0 a78,78 0 1,1 -156,0" />
</defs>
<text fill="hsl(calc(172 + var(--hue)) 45% 70% / .8)"
style="font-family:'Space Mono',monospace; font-size:7px; letter-spacing:2px;">
<textPath href="#tp">KINETIC CIRCLE · SPECIMEN Nº 001 · ORBITAL MOTION STUDY · CIRCULAR FORM — 360° ·
</textPath>
</text>
</svg>
</div>
<div class="layer" data-depth="12">
<div class="ring-outer" id="ringOuter"><span class="comet"></span></div>
</div>
<div class="layer" data-depth="20">
<div class="orb orb-a" id="orbA"><i></i></div>
<div class="orb orb-b" id="orbB"><i></i></div>
<div class="orb orb-c" id="orbC"><i></i></div>
</div>
<div class="layer" data-depth="26">
<button class="core" id="core" aria-label="Fire a pulse">
<span class="core-glow"></span>
<span class="core-surface"></span>
<span class="core-ring"></span>
</button>
</div>
</div>
</div>
<div class="row3">
<aside class="panel">
<p class="panel-title">CONTROL DIAL</p>
<label class="ctl">
<span class="ctl-label">VELOCITY <output id="outVel">100%</output></span>
<input type="range" id="vel" min="0" max="300" value="100" aria-label="Orbit velocity">
</label>
<label class="ctl">
<span class="ctl-label">SPECTRUM <output id="outHue">0°</output></span>
<input type="range" id="hue" min="0" max="360" value="0" aria-label="Colour spectrum shift">
</label>
<div class="btns">
<button class="btn" id="pulseBtn">◉ Pulse</button>
<button class="btn" id="pauseBtn" aria-pressed="false">❙❙ Hold</button>
</div>
</aside>
<footer class="hud">
<span>ORBIT θ <b id="hudAngle">000°</b></span>
<span>DRIFT <b id="hudVel">100%</b></span>
<span>PULSES <b id="hudPulse">0</b></span>
<span id="hudState">● ACTIVE</span>
</footer>
</div>
</div>
<script>
(() => {
const $ = s => document.querySelector(s);
const RM = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = $('#sky'), ctx = canvas.getContext('2d');
const orbit = $('#orbit'), core = $('#core');
const layers = [...document.querySelectorAll('.layer')];
let W, H, HUE = 0, velMult = 1, paused = false, pulses = 0;
/* ── canvas: stars, dust, sparks, shockwaves ── */
let stars = [], dust = [], sparks = [], shocks = [];
const hsl = (h, s, l, a = 1) => `hsla(${(h + HUE + 360) % 360}, ${s}%, ${l}%, ${a})`;
function resize() {
const d = Math.min(devicePixelRatio || 1, 2);
W = innerWidth; H = innerHeight;
canvas.width = W * d; canvas.height = H * d;
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
ctx.setTransform(d, 0, 0, d, 0, 0);
seed();
}
function seed() {
const n = Math.min(230, Math.round(W * H / 9000));
stars = Array.from({ length: n }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: .3 + Math.random() * 1.3, a: .15 + Math.random() * .55,
p: Math.random() * 6.28, s: .4 + Math.random() * 1.6,
vx: (Math.random() - .5) * 4, vy: (Math.random() - .5) * 4,
tint: Math.random() < .12
}));
dust = Array.from({ length: 14 }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: 1.6 + Math.random() * 1.8, a: .04 + Math.random() * .08,
vx: (Math.random() - .5) * 3, vy: -(3 + Math.random() * 8)
}));
}
function pulse() {
pulses++; $('#hudPulse').textContent = pulses;
const r = orbit.getBoundingClientRect();
const cx = r.left + r.width / 2, cy = r.top + r.height / 2, cr = r.width * .15;
shocks.push({ x: cx, y: cy, r: cr * 1.04, a: .85, w: 2, s: 640 });
shocks.push({ x: cx, y: cy, r: cr * .98, a: .5, w: 1, s: 900 });
for (let i = 0; i < 26; i++) {
const ang = Math.random() * 6.28, sp = 120 + Math.random() * 330;
sparks.push({
x: cx, y: cy,
vx: Math.cos(ang) * sp, vy: Math.sin(ang) * sp,
life: .5 + Math.random() * .8, max: 1.3,
c: [40, 172, 8][i % 3]
});
}
core.classList.add('flare');
clearTimeout(core._ft);
core._ft = setTimeout(() => core.classList.remove('flare'), 620);
}
/* ── ring rotators (JS-driven so dials control everything) ── */
const rot = [
{ el: $('#sweep'), spd: 24, dir: 1, a: 0 },
{ el: $('#ringOuter'), spd: 12, dir: 1, a: 0 },
{ el: $('#ticks'), spd: 6, dir: -1, a: 0 },
{ el: $('#textRing'), spd: 4, dir: 1, a: 0 },
{ el: $('#orbA'), spd: 42, dir: 1, a: 20 },
{ el: $('#orbB'), spd: 27, dir: -1, a: 120 },
{ el: $('#orbC'), spd: 16, dir: 1, a: 260 }
];
/* ── parallax ── */
let tx = 0, ty = 0, px = 0, py = 0;
addEventListener('pointermove', e => {
tx = (e.clientX / W - .5) * 2;
ty = (e.clientY / H - .5) * 2;
});
/* ── main loop ── */
let last = performance.now(), t = 0;
function frame(now) {
const dt = Math.min((now - last) / 1000, .05); last = now; t += dt;
if (!paused) for (const o of rot) { o.a = (o.a + o.spd * o.dir * velMult * dt) % 360; o.el.style.transform = `rotate(${o.a}deg)`; }
$('#hudAngle').textContent = String(Math.round((rot[0].a % 360 + 360) % 360)).padStart(3, '0') + '°';
px += (tx - px) * Math.min(1, dt * 4); py += (ty - py) * Math.min(1, dt * 4);
for (const l of layers) l.style.transform = `translate3d(${px * l.dataset.depth}px, ${py * l.dataset.depth}px, 0)`;
// sky
ctx.clearRect(0, 0, W, H);
const amp = RM ? .05 : 1;
for (const s of stars) {
if (!paused) { s.x = (s.x + s.vx * dt + W) % W; s.y = (s.y + s.vy * dt + H) % H; }
ctx.globalAlpha = s.a * (1 - amp * .45 + amp * .45 * Math.sin(t * s.s + s.p));
ctx.fillStyle = s.tint ? hsl(172, 70, 80) : '#dceaf2';
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.fill();
}
for (const d of dust) {
if (!paused) { d.x = (d.x + d.vx * dt + W) % W; d.y = (d.y + d.vy * dt + H) % H; }
ctx.globalAlpha = d.a; ctx.fillStyle = hsl(172, 60, 75);
ctx.beginPath(); ctx.arc(d.x, d.y, d.r, 0, 7); ctx.fill();
}
for (let i = sparks.length - 1; i >= 0; i--) {
const p = sparks[i];
if (!paused) { p.x += p.vx * dt; p.y += p.vy * dt; p.vx *= .985; p.vy *= .985; }
p.life -= dt;
if (p.life <= 0) { sparks.splice(i, 1); continue; }
ctx.globalAlpha = Math.max(0, p.life / p.max);
ctx.fillStyle = hsl(p.c, 90, 66);
ctx.beginPath(); ctx.arc(p.x, p.y, 1.8, 0, 7); ctx.fill();
}
const maxR = Math.hypot(W, H) / 2;
for (let i = shocks.length - 1; i >= 0; i--) {
const s = shocks[i];
if (!paused) s.r += s.s * dt * (.55 + velMult * .55);
s.a -= dt * 1.05;
if (s.a <= 0 || s.r > maxR) { shocks.splice(i, 1); continue; }
ctx.globalAlpha = 1;
ctx.lineWidth = s.w; ctx.strokeStyle = hsl(40, 95, 66, s.a);
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.stroke();
}
ctx.globalAlpha = 1;
requestAnimationFrame(frame);
}
/* ── interactions ── */
addEventListener('pointerdown', e => {
if (e.target.closest('.panel, input, button, .hud, .btn')) return;
pulse();
});
core.addEventListener('click', pulse);
$('#pulseBtn').addEventListener('click', pulse);
addEventListener('keydown', e => {
if (e.code === 'Space' && !/INPUT|BUTTON|TEXTAREA/.test(e.target.tagName)) {
e.preventDefault(); if (!e.repeat) pulse();
}
});
$('#pauseBtn').addEventListener('click', e => {
paused = !paused;
e.currentTarget.setAttribute('aria-pressed', paused);
e.currentTarget.textContent = paused ? '▶ Resume' : '❙❙ Hold';
const st = $('#hudState');
st.textContent = paused ? '● HELD' : '● ACTIVE';
st.classList.toggle('held', paused);
});
const fill = el => el.style.setProperty('--fill', (el.value - el.min) / (el.max - el.min) * 100 + '%');
$('#vel').addEventListener('input', e => {
velMult = e.target.value / 100;
$('#outVel').textContent = $('#hudVel').textContent = e.target.value + '%';
fill(e.target);
});
$('#hue').addEventListener('input', e => {
HUE = +e.target.value;
document.documentElement.style.setProperty('--hue', HUE);
$('#outHue').textContent = HUE + '°';
fill(e.target);
});
addEventListener('resize', resize);
resize();
fill($('#vel')); fill($('#hue'));
requestAnimationFrame(frame);
setTimeout(pulse, 700); // welcome flare
})();
</script>
</body>
</html>
<script type="text/plain" id="launch-params">
llama-cli --prompt create a fancy circle in html --seed 1337 --single-turn --output output.txt --flash-attn on --load-mode dio --fit on --parallel 1 --threads 32 --fit-target 0 --n-gpu-layers all --cpu-strict 1 --log-verbosity 3 --batch-size 2048 --ubatch-size 512 --spec-type draft-mtp --spec-draft-n-max 5 --cache-type-k-draft q4_0 --cache-type-v-draft q4_0 --model /run/media/caps/extra/llm-models/Qwen3.8-27B-UD-Q4_K_XL.gguf --cache-type-k q8_0 --cache-type-v q8_0 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --temperature 1.0 --top-p 0.95 --presence-penalty 0.0 --reasoning on --reasoning-effort xhigh
</script>
<script type="text/plain" id="chat">
User:
create a fancy circle in html
Assistant:
[Start thinking]
The user is asking for "a fancy circle in HTML." Simple request, but I want to make it a rich, polished piece — not a plain static div with a border-radius.
Let me think about what a "fancy circle" could be. It's an open creative prompt. I should build something where the circle is the star: concentric rings, rotating orbital elements, particles, glow, interactive (mouse parallax, clicks generate ripples, hover effects), maybe animated with canvas or pure CSS/SVG.
Design direction: circle as the protagonist — a cosmic/celestial aesthetic, or a "dial"/instrument aesthetic? Let me think of something distinctive. Options:
1. **Celestial/orrery** — planet with orbiting moons, rings, stars, parallax. Rich, alive.
2. **Kinetic typography ring** — rotating text on a circular path around a core.
3. **Audio visualizer-esque circle** — pulsing radial bars (fake, no audio).
4. **Instrument/dial** — gauges, tick marks, needle, HUD-style.
I can combine: a central glowing orb with concentric rotating rings (dashed, solid, tick marks), circular rotating text, orbiting satellite dots, particle field in background, mouse-follow parallax, click to generate ripple pulse, controls to change hue/speed? Interactivity: mouse move tilts/parallaxes, click sends a shockwave through the rings, maybe a small control strip (speed, hue) — that gives responsive feedback to input.
Typography: pair a distinctive display face with a readable body face. Google Fonts: display could be "Unbounded," "Monoton" (too flashy), "Syne," "Zen Dots," "Orbitron." Body: "Space Grotesk," "IBM Plex Mono" for data readouts. For a celestial instrument, "Unbounded" for display + "Space Mono" or "IBM Plex Mono" for labels feels right. Actually a monospace for HUD readouts + a display for the title works well.
Palette: I want a deep cosmic but not just near-black + one neon. Let's use a deep teal-ink background with multiple hues: warm amber/gold core, cyan ring, coral accent — a multi-hue scheme on deep blue-green. E.g., background #0b1220? Let's go: base #0e1a24 (deep petrol), accent gold #ffc466, cyan #6fe3d4, coral #ff7a6b. Multiple accents so it doesn't read as a single neon.
Structure:
- Full-viewport scene, circle at center (it's the star of the show, so a center circle is appropriate).
- Background: layered — a starfield (canvas), a slow drifting radial glow, faint grid or noise.
- Center: layered rings:
- Outer dashed ring slowly rotating
- Tick-mark ring (SVG or conic gradient) rotating opposite direction
- Text ring (SVG textPath) "SPECIMEN · KINETIC · CIRCLE · " rotating
- Orbiting particles at different radii/speeds
- Core: pulsing radial-gradient orb, breathing, with an inner highlight — like a small sun/star.
- Parallax: on mouse move, layers shift at different depths; the core shifts a little.
- Click/press: emit a shockwave — expanding ring + the core briefly flares + the orbiting satellites get a velocity boost (temporarily).
- Controls: a small panel bottom-left or bottom with:
- Speed slider
- Hue shift slider or "tint" buttons
- Pause/play?
Keep it tasteful. A "dial" style control strip.
- Readout: a small HUD showing rotation angle, number of ripples, etc. in monospace. That adds life.
Implementation: a single HTML file with CSS + JS. Canvas for starfield + ripple particles. DOM/CSS for rings (easier: SVG for textPath and ticks). Use CSS custom properties for hue so a slider can re-color live.
Let me make it truly polished.
Key components:
```html
<canvas id="stars"></canvas> <!-- starfield + click ripples + floating dust -->
<main class="stage">
<header> small brand top-left: "ORBITAL / 01" + description top-right? </header>
<div class="orbit" id="orbit">
... rings ...
</div>
<aside class="panel"> controls </aside>
<footer class="hud"> readouts </footer>
</main>
```
Rings (inside .orbit, absolutely positioned and centered):
1. `.ring.r1` — a large thin solid ring, slow rotation, with a small gap (conic gradient mask or border + rotating dot).
2. `.ticks` — SVG circle with dashed stroke to create ticks, rotating in reverse.
3. `.text-ring` — SVG textPath circular text, rotating.
4. `.orbiters` — 3-4 dots with CSS animation on a wrapper's rotation, offset on different radii (using transform: rotate(a) translateX(r) rotate(-a)? Simpler: each orbit dot sits in its own rotating container with a different duration; the dot is positioned at top center of that container with a size = 2*radius).
5. `.core` — the orb: layered radial gradient, breathing scale animation, glow, inner ring.
6. `.shockwave` container — JS adds an element to spawn an expanding ring.
Parallax: on mousemove, set CSS vars --mx, --my in [-1,1]; layers use transform: translate(calc(var(--mx)*Npx), calc(var(--my)*Npx)). Need to be careful: elements with their own rotate animations can't easily get a parallax transform on the same element — nest them: outer wrapper for parallax, inner for rotation. Structure: `.layer` (parallax translate, transition or rAF lerp for smoothness) > animated child.
Smooth parallax via rAF lerp for buttery feel.
Click shockwave: on pointerdown on the stage (not on panel), spawn a `.wave` at the center (or at the click position relative to center?). Simpler and prettier: wave emanates from the core. The core also flares (scale + brighter). Also spawn a burst of particles in the canvas at the center. And increment a "pulses" counter. Also a quick speed kick to the rings (temporarily multiply speed by adding a class that reduces animation-duration? Changing animation-duration mid-animation causes jumps. Better: keep rings CSS-animated and apply a temporary scale "flare" — visual feedback is enough. Or drive ring rotation in JS via rAF so speed is fully controllable. Hmm.
Driving rotation in JS: gives us real speed control (slider) and kick. Let's do it: a single rAF loop updates:
- angle += speed * dt for each ring (with per-ring multipliers), applied via style.transform rotate.
- Core breathing is fine as CSS.
- Lerp parallax.
- Canvas starfield (twinkle, drift) and ripple particles and shockwave rings (canvas-drawn shockwaves might be prettier: expanding stroked circle with fading alpha; plus a DOM one for a glow ring). Canvas shockwave is fine.
Canvas: one canvas behind DOM, for stars/dust, one canvas on top for shockwaves? Can do in one canvas: draw stars + shockwaves (shockwaves above stars but below DOM rings — canvas is behind DOM, that's fine).
Actually let me keep it simple: one canvas for stars, ripples, shockwave rings. Behind the .orbit.
Control panel (bottom-left, a vertical stack or a bottom bar):
- "VELOCITY" slider 0–300%
- "HUE" slider 0–360 or preset swatches? Hue slider is fun: sets --hue that shifts the gold/cyan via color-mix? Simpler: define accent colors in HSL and offset hue variable: --h-base. Colors: gold hsl(38 + hue, ...), cyan hsl(170 + hue,...), coral hsl(8 + hue,...). CSS calc on hue in hsl() works: hsl(calc(38 + var(--hue)) 90% 65%). That works in modern browsers.
- "PAUSE" toggle button, and maybe "BURST" button (manually triggers a pulse).
- Keep panel compact, monospace labels, custom-styled sliders.
HUD (bottom-right or top-right): live values — angle (deg), velocity %, pulses count, maybe "state: ACTIVE".
Header (top): a display-typeface title — like "The Kinetic Circle" — top-left with a small kicker, and a short description top-right. Since the circle is the star, I keep the text minimal and put it in the corners.
Reduced motion: respect prefers-reduced-motion — stop the auto rotation? Provide at least toned down. I'll add a media query to disable CSS animations and let JS check it to reduce speed. Simple: if reduced, default speed 0 and no star twinkle motion. Keep it light.
Fonts: Google Fonts link: "Unbounded" (700, 400) for display — distinctive, geometric, fits the orbital theme. Body/labels: "Space Grotesk" or a monospace "Space Mono"? Use "Unbounded" for title, "Space Mono" for HUD/labels — pair a distinctive display with a readable mono. Body copy minimal. Let's use Unbounded + Space Mono. Two families, strong contrast.
Layout: full viewport, no scroll (100vh). Corners have the content. On small screens, stack the panel to bottom, title smaller.
Background layers:
- Base: deep petrol #0a141c with a subtle vignette (radial-gradient darker at edges).
- Canvas stars: ~140 stars, varying radii, twinkle (sin alpha), slow drift; some larger "dust" particles slowly rising.
- Faint conic/ambient glow behind the orbit, hue-shifting (slowly rotating CSS animated hue? Or a static radial glow in gold/cyan at low opacity).
- A faint grid? Maybe skip grid; a very subtle "ecliptic" line across the scene — a thin diagonal line with gradient — adds a compositional touch. Or a faint crosshair through the center. A thin horizontal + vertical hairline through center at very low opacity, dashed — instrument feel. Nice.
Orbit size: min(70vmin) diameter base.
Rings detail:
- r1 (outermost): thin 1px ring, rgba stroke; contains a "comet" dot that travels (a child dot at top). Rotates at 1x.
- tick ring: SVG 360 ticks? Use a circle with pathLength and stroke-dasharray to fake ticks: e.g. circle r=..., pathLength=144, stroke-dasharray="0.5 0.5" pathLength trick: dasharray in pathLength units: 1 tick every 2.5 units → 58 ticks... let me set pathLength="360", stroke-dasharray="1 5" → 60 ticks each 1 unit long. Stroke-width to control length.
- Second tick ring, sparser, longer ticks: pathLength=360 dasharray="2 38" → 9 ticks.
- Text ring: SVG <text><textPath href="#circ"> repeating "KINETIC • CIRCLE • SPECIMEN 01 • ORBITAL MOTION • " sized to fit. Rotate slowly in reverse.
- Orbiters: 3 satellites on different radii, durations 9s, 15s, 26s, different sizes/colors (gold, cyan, coral), each with a small trail? Trail via box-shadow or a small conic gradient? Keep it: dot + glow, plus a short arc behind — can use a wrapper with a partial border. Simpler: dot with blur shadow is enough; a "comet tail" via a linear-gradient pseudo-element also possible.
Core:
- Size ~26% of orbit.
- Layers:
- Outer glow (box-shadow / radial) breathing (scale 1↔1.06, 4s).
- Orb: radial-gradient (warm white center → gold → coral rim) with slight offset highlight (another radial at 30% 30%).
- Inner thin ring (ring inset via ::after).
- A rotating subtle texture? A rotating conic gradient at low opacity over the orb (like slow "surface swirl") — nice touch: .core-surface {background: conic-gradient(...); mix-blend: overlay; animation spin 18s}.
- Flare on pulse: JS adds .flared class for 600ms → scale 1.08 + brighter via filter.
Shockwave: canvas: on pulse, push {r: coreR, alpha:0.9}; each frame r += 900*dt? Actually faster: r += (r*2+600)*dt; alpha decays. Draw 2-3 rings with different widths. Also burst particles from center: 24 particles with random velocities, fade.
Click anywhere (except panel) to trigger pulse; spacebar also triggers.
Cursor: crosshair over stage? Custom cursor a small circle following? Might be nice: a small ring cursor. Keep default pointer? Add a subtle custom cursor dot in canvas at mouse position. Hmm, could conflict. Skip custom cursor; use crosshair over stage — instrument feel.
HUD values updating each frame: θ = (angle % 360).toFixed(0)°, velocity%, pulses, FPS too? Keep: ORBIT θ, VELOCITY, PULSES, plus "HUE".
Panel styling: thin border, slightly translucent bg but not full glassmorphism blur everywhere; a subtle backdrop is OK if not heavy glass. Use solid dark with 1px border and a corner accent. Custom slider: webkit-styled with a square thumb.
Title: top-left. Something like a kicker "SPECIMEN NO. 001 — CIRCULAR FORM" then large "The Kinetic Circle". Right side: a short 2-line description + a "press / space to pulse" hint.
Accessibility: buttons with aria-labels, slider labels, focus-visible styles.
Mobile: shrink orbit to 82vmin; title scales with clamp; panel becomes a bottom bar with horizontal layout? Keep the panel as a compact column at bottom-left, on very small screens allow it to shrink (small font). Use clamp() everywhere.
Alright let me code carefully.
Structure:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Kinetic Circle — Specimen 001</title>
<link Google fonts: Unbounded:wght@400;700;900 & Space Mono:wght@400;700>
<style> ... </style>
</head>
<body>
<canvas id="sky"></canvas>
<div class="frame">
<header class="head">
<div class="head-left">
<p class="kicker"><span class="tick-dot"></span> SPECIMEN Nº 001 — CIRCULAR FORM</p>
<h1 class="title">The <em>Kinetic</em> Circle</h1>
</div>
<div class="head-right">
<p>A living study in orbital motion — every ring turns at its own tempo, the core breathes, and the field answers your touch.</p>
<p class="hint"><kbd>click</kbd> / <kbd>space</kbd> to fire a pulse</p>
</div>
</header>
<div class="stage" id="stage">
<div class="cross cross-h"></div>
<div class="cross cross-v"></div>
<div class="orbit" id="orbit">
<div class="layer" data-depth="14">
<div class="ring ring-outer"><span class="comet"></span></div>
</div>
<div class="layer" data-depth="10">
<svg class="ticks" viewBox="0 0 200 200">
<circle cx=100 cy=100 r=99 pathLength=360 .../>
</svg>
</div>
<div class="layer" data-depth="22">
<svg class="text-ring" viewBox="0 0 200 200">
<defs><path id="tp" d="M100,100 m-78,0 a78,78 0 1,1 156,0 a78,78 0 1,1 -156,0"/></defs>
<text><textPath href="#tp">KINETIC CIRCLE · SPECIMEN 001 · ORBITAL MOTION · CIRCULAR FORM ·</textPath></text>
</svg>
</div>
<div class="layer" data-depth="18">
<div class="orbiters">
<div class="orb orb-a"><i></i></div>
<div class="orb orb-b"><i></i></div>
<div class="orb orb-c"><i></i></div>
</div>
</div>
<div class="layer" data-depth="26">
<button class="core" id="core" aria-label="Fire a pulse">
<span class="core-glow"></span>
<span class="core-surface"></span>
<span class="core-ring"></span>
</button>
</div>
</div>
</div>
<aside class="panel">
<p class="panel-title">CONTROL DIAL</p>
<label class="ctl">
<span class="ctl-label">VELOCITY <output id="outVel">100%</output></span>
<input type="range" id="vel" min="0" max="300" value="100">
</label>
<label class="ctl">
<span class="ctl-label">SPECTRUM <output id="outHue">0°</output></span>
<input type="range" id="hue" min="0" max="360" value="0">
</label>
<div class="btns">
<button id="pulseBtn" class="btn">⦿ Pulse</button>
<button id="pauseBtn" class="btn" aria-pressed="false">❚❚ Hold</button>
</div>
</aside>
<footer class="hud">
<span>ORBIT&nbsp;θ <b id="hudAngle">0°</b></span>
<span>DRIFT <b id="hudVel">100%</b></span>
<span>PULSES <b id="hudPulse">0</b></span>
<span class="hud-state" id="hudState">● ACTIVE</span>
</footer>
</div>
<SCRIPT_ESCAPE> ... </SCRIPT_ESCAPE>
</body>
</html>
```
CSS variables:
```css
:root{
--hue: 0;
--bg: #0a141b;
--ink: #dfeef2;
--mut: #6d8b96;
--gold: hsl(calc(40 + var(--hue)) 95% 66%);
--cyan: hsl(calc(172 + var(--hue)) 75% 62%);
--coral: hsl(calc(8 + var(--hue)) 90% 64%);
--line: hsl(calc(190 + var(--hue)) 30% 45% / .22);
}
```
Note: `hsl(calc(...) ...)` — calc on hue works. Space syntax is also fine.
Background:
```css
body{
background:
radial-gradient(1200px 800px at 50% 42%, hsl(calc(172+var(--hue)) 40% 14% / .55), transparent 60%),
radial-gradient(900px 700px at 78% 84%, hsl(calc(40+var(--hue)) 45% 12% / .4), transparent 60%),
var(--bg);
}
```
Multi-hue ambient. Plus a vignette overlay on .frame::before, or a body::after with a radial darkening the edges.
Canvas #sky: fixed inset 0, z-index 0. .frame z-index 1.
Frame layout: position fixed inset 0, padding: clamp(16px, 3vw, 40px); display grid rows auto 1fr auto; the stage takes the middle.
Header: grid columns 1fr auto, align start, gap.
Title: font Unbounded 900, clamp(1.6rem, 4.2vw, 3rem); em is italic? Unbounded has no italic; make em gold color instead. A mixed case for character: "The Kinetic Circle" with "Kinetic" gold + outline? Can use -webkit-text-stroke for "CIRCLE" as outline: .title em { -webkit-text-stroke: 1.5px var(--gold); color: transparent }. That's distinctive. Keep "Kinetic" filled gold.
Hmm, title could be two lines: "THE KINETIC" / "CIRCLE"? Keep on one line with wrap allowed.
Right column: text-align right, max-width 34ch, font Space Mono 12-13px, muted. Hint: kbd styled with border.
Stage: position relative; place-items center; display grid; cursor: crosshair.
Crosshairs: absolutely positioned lines through center:
.cross-h{position:absolute; left:0; right:0; top:50%; height:1px; background: linear-gradient(90deg, transparent, var(--line) 20% 80%, transparent); }
Same for vertical. Subtle.
Orbit: position relative; width: min(74vmin, 640px); aspect-ratio 1.
Layers: position absolute inset 0; will-change transform; (transform set by JS: translate3d + scale? no scale). Parallax translate in rAF.
Ring-outer: inset 0; border:1px solid var(--line); border-radius:50%; a comet child: position absolute; top:-3px? Place at top center: left:50%; top:0; transform:translate(-50%,-50%); width:7px;height:7px; bg gold; glow shadow. The ring rotates → comet orbits. But the ring rotates with JS (transform rotate) — layer already has parallax translate; rotating the ring itself is fine.
Wait: if JS sets ring element transform: rotate(a), and layer has parallax translate — good separation.
Ticks SVG: circle cx100 cy100 r 98, fill none, stroke var(--line), stroke-width 2.5? For ticks pointing inward? The dashes are along the circumference — they read as radial ticks if the stroke-width is short... actually the dashes are arcs (tangential), looking like segments along the circle — acceptable as a tick ring. To get radial tick lines, a better way: a circle with stroke-width = length of the tick (e.g. 6) and a small dash length (0.8) → radial-ish rectangles. With pathLength 360 dasharray "0.6 5.4" stroke-width 5 → 60 radial ticks. Yeah, that's the classic trick.
Two tick circles:
- outer fine: r=98, stroke-width 5, dasharray .55 5.45 (60 ticks), opacity .5
- inner sparse: r=92? Actually put the sparse longer ticks: r=95 stroke-width 12 dasharray 1 39 (9 ticks) different color (cyan, opacity .35).
The text ring: at a radius between ticks and orbiters? Orbit viewBox 200, r=78 text, font Space Mono, letter-spacing, size ~9.5, fill var(--mut) or a gold-ish muted. textLength? Just tune. The rotation of the text ring is reverse direction.
Font size: circumference 2π*78 ≈ 490 units. Text "KINETIC CIRCLE · SPECIMEN 001 · ORBITAL MOTION · CIRCULAR FORM ·" ≈ 60 chars. At font-size 9 with spacing 2... in SVG user units (200 box scaled to 74vmin, so at 640px display that's 1 user unit ≈ 3.2px; font-size 9 → ~29px on screen — too big? 9 units * 3.2 = 28.8px. Let's pick font-size 6.5 with letter-spacing 1.5. Chars ≈ 60 → avg width ~ (6.5*0.6 + 1.5) ≈ 5.4 units → 60*5.4 = 324 units < 490 → gap. Add more text or increase size: font-size 7.5, ls 2 → width (4.5+2)=6.5*60=390. Still gap. Longer text: "KINETIC CIRCLE · SPECIMEN Nº 001 · ORBITAL MOTION STUDY · CIRCULAR FORM · " (~71 chars) *6.5 ≈ 461 — close to 490, small gap OK, or I can use textLength with lengthAdjust="spacing" to fill exactly. Use textLength="486" to force fill? textLength on textPath is supported in modern browsers (with some quirks). Simpler: let it be; a small gap in rotating text is visually fine (the gap itself looks like a feature — actually a clean seam looks like a bug; but for a rotating ring a small gap reads as design). Fine.
Orbiters: each .orb {position:absolute; inset:0; border-radius:50%;} with a different width: orb-a inset 8% (radius smaller?), hmm — let's set a size per orb:
- .orb-a{ inset: -6%; } (outside the outer ring? Outer ring is at orbit's inset 0 = 50% radius; -6% goes beyond orbit container but fine, overflow visible)
Actually simpler: define a size per orb:
- .orb-a{ width:112%; height:112%; left:-6%; top:-6%; animation 14s }
- .orb-b{ width:78%; height:78%; left:11%; top:11%; 22s reverse}
- .orb-c{ width:128%; height:128%; left:-14%; top:-14%; 34s }
Each orb contains an <i> at top center of that circle: i{position:absolute; top:0; left:50%; translate:-50% -50%; width:8px...; }
Orb rotation is via CSS animation (independent of the JS speed?). But the velocity slider should affect the whole system — better to drive orbiters in JS too: give each a data-speed and JS rotates them. Yes, drive all ring rotations in JS for unified velocity control: an array of {el, mul, dir}.
So .ring-outer, .ticks (SVG rotate via CSS transform on the svg element), .text-ring, .orb-a/b/c — all rotated via JS: el.style.transform = `rotate(${a}deg)`.
Comet on outer ring: fine.
Satellites (i element) colors: gold 7px, cyan 5px, coral 4px, with glow shadow; add a small "tail": i::after arc? Skip; add a pulsing shadow.
Core button: width 30%; aspect 1; border-radius 50%; border 1px solid hsl gold/40; background: radial-gradient(circle at 34% 30%, #fff8ec, var(--gold) 34%, var(--coral) 72%, hsl(calc(8+var(--hue)) 70% 22%) 100%); box-shadow: 0 0 60px hsl(gold/.45), 0 0 140px hsl(gold/.2), inset 0 0 40px rgba(255,255,255,.25)? Inset shadow might dull it; keep. Breathing animation on the core (scale 1→1.045, 4.6s ease-in-out infinite) — but I also want flare scale... the breathe animation on the button and the flare on .core-glow via class. Flare: .core.flare .core-glow { opacity 1; } etc.
.core-glow: inset -30%; radial gold → transparent; animation pulse 4.6s same timing (scale); opacity .7; transition.
.core-surface: inset 12%; border-radius 50%; background: conic-gradient(from 0deg, transparent 0 40deg, hsl(gold/.35) 90deg, transparent 160deg, hsl(coral/.3) 260deg, transparent 320deg); mix-blend: screen? Over a bright orb, screen brightens — OK; animate spin 16s linear infinite.
.core-ring: inset 6%; border 1px solid rgba(255,255,255,.35); border-radius 50%.
Core is <button> — reset default styles. Focus-visible outline: 2px dashed gold, offset.
Panel: position absolute bottom-left of frame (in grid row 3 col 1?). Frame grid: rows auto 1fr auto; row 3: panel left, hud right: grid-column 1 / -1; display flex; justify-content space-between; align-items flex-end; gap.
Panel width ~230px; border:1px solid var(--line); padding 14px 16px; background: hsl(bg-ish / .6); position relative; corner accent: .panel::before{content:""; position absolute; top:-1px; left:-1px; width:10px;height:10px; border-top:2px solid var(--gold); border-left:2px solid var(--gold)}.
panel-title: Unbounded 700 10px letter-spacing .2em, muted color, a small dot before it.
ctl-label: Space Mono 10px letter-spacing .15em muted; output gold, float right.
Range styling:
```css
input[type=range]{ -webkit-appearance:none; width:100%; height:2px; background: linear-gradient(90deg, var(--gold), var(--cyan)); outline offset... }
```
Better: track = 2px line of var(--line) with filled portion? Filled portion needs a JS var: set --fill on input via JS. Let's do: background: linear-gradient(to right, var(--gold) var(--fill,50%), hsl(.../.25) var(--fill,50%)). Update on input.
Thumb: -webkit-slider-thumb: appearance none; width 12px;height 12px; background var(--bg); border 2px solid var(--gold); border-radius 50%; cursor: ew-resize; on hover: bg gold.
Buttons: .btn Space Mono 11px, uppercase, border 1px solid var(--line), padding 8px 12px, bg transparent, hover: border-color gold, color gold, transition; active translateY(1px). pauseBtn[aria-pressed=true] → gold-filled.
HUD: Space Mono 11px, letter-spacing .12em, muted; b{color var(--ink) font-weight 400}; spans separated by gap 18px; hud-state in gold with a pulsing dot (animation blink 2s). When paused → state "● HELD" coral color.
Responsive:
@media (max-width: 720px): header stacks (grid rows auto auto, right column left aligned, title smaller), orbit min(80vmin), panel full-width row: display flex wrap? Panel becomes a horizontal: .panel{display:grid; grid-template-columns: 1fr 1fr; }? Simpler: on small screens, row 3 becomes column-reverse? Let's do: .frame > .row3 { flex-direction: column; align-items: stretch; gap 12px } panel width auto; hud justify space-between. And header right text-align left, hide the description? Keep but smaller. Also hide the head-right hint? Keep, it's short.
Reduced motion:
```css
@media (prefers-reduced-motion: reduce){
.core, .core-glow, .core-surface { animation: none }
}
```
JS: const RM = matchMedia('(prefers-reduced-motion: reduce)').matches; if RM → default velocity 30 and no star drift/twinkle amplitude reduced; keep functionality.
JS:
```js
const $ = s => document.querySelector(s);
const stage = $('#stage'), orbitEl = $('#orbit');
const canvas = $('#sky'), ctx = canvas.getContext('2d');
let W,H,DPR;
function resize(){ DPR = Math.min(devicePixelRatio||1, 2); W=innerWidth; H=innerHeight; canvas.width=W*DPR; canvas.height=H*DPR; canvas.style.width=W+'px'; ...; ctx.setTransform(DPR,0,0,DPR,0,0); initStars(); }
```
Stars: N = area-based (W*H/9000, cap 220). Each {x,y,r: .4-1.4, base alpha .2-.8, tw: random phase, twS: .5-1.5, drift: small vx,vy}.
Bigger dust: 12 particles r 1.5-2.5 slowly rising, alpha .15.
Shocks: array {r, a, w, speed}
Sparks: array {x,y,vx,vy,life,color}
Pulse():
- pulses++
- shocks.push({r: coreRadiusPx*1.02, a:.8, w:2, s: 700})
- shocks.push({r: coreRadiusPx, a:.5, w:1, s: 900}) (a second thin one)
- sparks: 26 particles from center, random angle, speed 120-420, life 0.6-1.2s, colors picked from gold/cyan/coral (using hsl string with current hue — compute from getComputedStyle? Simpler: store hue in a JS var HUE and construct `hsl(${(40+HUE)%360} 95% 66%)` etc. Keep HUE in sync with the slider.)
- Core flare: coreBtn.classList.add('flare'); setTimeout remove after 650.
- HUD pulses update.
coreRadiusPx: orbitEl.getBoundingClientRect().width * 0.15 (core is 30% of orbit → radius 15%).
Rotators:
```js
const rotators = [
{el: $('#ringOuter'), speed: 12, dir: 1}, // deg/sec base
{el: ticksSvg, speed: 6, dir: -1},
{el: textRing, speed: 4, dir: 1}, // slow text
{el: orbA, speed: 40, dir: 1},
{el: orbB, speed: 26, dir: -1},
{el: orbC, speed: 16, dir: 1},
];
```
Base degrees/sec. angle += speed*dir*velMult*dt*(paused?0:1). velMult from slider (0-3).
Parallax: track pointer: tx = (e.clientX/W - .5)*2; lerp px += (tx-px)*min(1, dt*4). Apply to layers: for each .layer: d = dataset.depth; translate3d(px*d, py*d, 0). Apply to orbitEl whole slightly too? Layers alone is enough.
Also apply to crosshairs? No.
Hue slider: on input: document.documentElement.style.setProperty('--hue', v); HUE=v; outHue = v+'°'; update --fill.
Velocity slider: velMult = v/100; outputs; hudVel.
Pause: toggle paused; button aria-pressed; hudState text/class.
Keyboard: space or Enter triggers pulse (prevent space scroll). pointerdown on stage (including core) triggers pulse — but avoid double if the core is a button (it's inside stage; fine, one handler on stage, check e.target.closest('.panel, .btn, input, button')? core is a button; pressing it also pulses — OK, it's within stage so one handler covers; but keyboard Enter on core will trigger click event → also pulses, OK). Actually attach on stage: 'pointerdown' → if(!e.target.closest('input,button') ) pulse? Core is a button — allow it: if(!e.target.closest('input, .panel')) pulse(). Good.
Space key: window keydown, code 'Space' && !e.repeat → e.preventDefault(); pulse(). But if focus is on a button, space will trigger click → double. Check: if (e.target.tagName === 'INPUT' || e.target.tagName==='BUTTON') skip custom handling (let native handle; button's native click will also pulse via the click handler?). Let me add: pulseBtn click → pulse(). core button click → pulse(). Space on core button → click → pulse (native). Space on panel vel input → adjusts slider, fine. So custom space handler only if target is body/stage. Implement: if(e.code==='Space' && !/INPUT|BUTTON|TEXTAREA/.test(e.target.tagName)){ e.preventDefault(); pulse(); }
FPS-independent: use performance.now delta, clamp dt to 0.05.
Star rendering: clear; for each star: alpha = base * (0.6 + 0.4*sin(t*twS + tw)); x += drift*t... drift: x += vx*dt; wrap. Draw circle fillStyle white-ish, slight hue: some stars tinted cyan/gold (20%).
Draw shocks: for each: r += s*dt*(0.6+velMult*0.6)? Tie shock speed to velocity — fun. a -= dt*1.1; if r > maxDim or a<=0 remove. strokeStyle hsl(gold, a). lineW w. Also a second fainter ring with offset r*0.98 cyan.
Sparks: update position, vy += gravity? No gravity — space; just slow friction vx*=... life -= dt; alpha = life; draw 2px rect or small circle with glow (shadowBlur is slow; skip shadow, use alpha + tiny radius).
Draw order: stars, dust, sparks, shocks.
Loop:
```js
let last = performance.now(), t=0;
function frame(now){ dt = Math.min((now-last)/1000, .05); last=now; if(!paused) t += dt*velMult ... actually use real t for twinkle (always animate, even when paused? when paused rotation halts but stars keep twinkling = alive. yes, use real tGlobal += dt.)
...
requestAnimationFrame(frame) }
```
HUD angle: rotators[0].angle % 360 → hudAngle.textContent = `${deg.toFixed(0).padStart(3,'0')}°`. Update at most ~10Hz to avoid layout thrash? Small text updates every frame is fine, but pad with spaces? Use padStart with '0'. OK update every frame.
HUD pulses update only on change.
Init: resize(); addEventListener resize; start loop.
Also: initial "welcome" pulse after 600ms so the page feels alive immediately. Nice.
One more living detail: a subtle "scan" — a sweeping conic gradient beam behind the orbit that rotates (like radar), very low opacity. A .sweep element in orbit: inset -10%; background: conic-gradient(from 0deg, transparent 0 84%, hsl(gold/.10) 96%, transparent 100%); border-radius 50%; rotation via JS (fast, 30 deg/s). Adds subtle life. mix-blend screen.
Text ring color: fill: hsl(calc(172 + var(--hue)) 30% 65% / .8) cyan muted.
Also double-check the SVG ticks circle with pathLength: circle supports pathLength in modern browsers, yes.
textPath href: use xlink? Modern href is fine.
Fonts in SVG: font-family: 'Space Mono'; font-size 7px? Let me tune: r=78 → circumference 490. Text: "KINETIC CIRCLE — SPECIMEN Nº 001 — ORBITAL MOTION — CIRCULAR FORM — " let me count: about 66 chars with spaces. Space Mono char width = 0.6em. font-size 7 → 4.2 units + letter-spacing 1.6 = 5.8 * 66 = 383. Gap 107 (22%) — too much. font-size 8.5 → 5.1+1.8=6.9*66=455 → gap 35 (7%) — acceptable seam. Or textLength="490" lengthAdjust="spacing" to force? textLength on textPath: Chrome supports? textLength is on <text> and supported on textPath in recent Chrome/FF; Safari had issues. Risky. Keep natural gap; it reads as a design choice with a "•" at the end. Actually let me add a diamond "◆" marker at the end to make the seam intentional: include "· KINETIC CIRCLE · SPECIMEN Nº 001 · ORBITAL MOTION · CIRCULAR FORM ·" — since the string starts and ends with "·", the gap between the last "·" and first "K" reads as a spacing rhythm. Good.
Em-dash inside SVG text is fine.
Panel position: in grid row 3. .row3 { display:flex; justify-content: space-between; align-items:flex-end; gap:24px }.
Frame padding & grid:
```css
.frame{position:fixed; inset:0; display:grid; grid-template-rows:auto 1fr auto; padding:clamp(18px,3.5vw,44px); gap: 12px; z-index:2; pointer-events:none;}
```
Wait — pointer-events none on frame, and auto on children? Canvas is behind; stage should receive pointer events (pulse) — stage itself. If frame is pointer-events:none and stage is pointer-events:auto, clicks on empty frame areas pass to canvas (canvas is behind, receives? canvas pointer-events default auto, fine, but I attached the handler only on stage). Let me attach pointerdown to window instead and ignore if target is within .panel/.hud? Simpler: attach to document, check e.target.closest('.panel, header, .hud') → skip. That way clicks anywhere pulse. But clicking on the canvas also pulses — nice.
Make .frame pointer-events:none, and interactive children auto: header? Header text is non-interactive, keep it none so clicks pass through to pulse — cute (clicking the title also pulses). Panel and hud pointer-events:auto.
Stage cursor crosshair; body cursor default.
Vignette: .frame::after? Use a body::after fixed radial darkening the edges + a very subtle noise? Noise via an SVG feTurbulence data-URI background at low opacity — adds texture. Use:
```css
body::before{content:""; position:fixed; inset:0; z-index:1; pointer-events:none; opacity:.5; background: radial-gradient(120% 120% at 50% 45%, transparent 55%, rgba(4,10,14,.75) 100%);}
```
Z-order: canvas z0, vignette z1, frame z2.
Noise: skip or a tiny inline SVG turbulence with opacity .03 at z1. Include — cheap and adds craftsmanship:
```css
body::after{content:""; position:fixed; inset:0; z-index:1; pointer-events:none; opacity:.05; background-image:url("data:image/svg+xml,...feTurbulence...");}
```
Data URI: `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>` — need to URL-encode # as %23, % as %25. OK.
Selection color: ::selection gold on dark.
OK, core button default styles: appearance none, padding 0, background is set, border, cursor pointer.
Flare CSS:
```css
.core.flare{ animation: none? }
```
The core has a breathe animation on transform; flare should be additive. Put breathe on core (transform scale keyframes). Flare: apply filter brightness(1.5) saturate(1.2) + a glow boost on a transition:
```css
.core{ transition: filter .5s; }
.core.flare{ filter: brightness(1.6) saturate(1.3); }
.core.flare .core-glow{ opacity: 1; transform: scale(1.25); }
.core-glow{ transition: opacity .5s, transform .5s; }
```
The core-glow has its own breathe animation — animation overrides transform... if core-glow has an animation that animates transform, then .flare's transform won't apply (animation wins). So put breathe on .core (transform), and have .core-glow only pulse opacity via animation:
```css
.core-glow{ animation: glowPulse 4.6s ease-in-out infinite; opacity:.75 }
@keyframes glowPulse{ 50%{ opacity:1 } }
```
Flare adds transform scale — no conflict. But .core.flare filter + breathe animation on .core — filter and transform are independent, fine.
Remove flare via JS timeout 600ms; guard against re-adding (clear existing timeout).
Satellite i element: also a small pulse animation for shadow:
```css
i{ ... box-shadow: 0 0 10px 2px currentColor; color: var(--gold) }
```
Set color per orb; i uses background currentColor.
Trail effect on satellites: skip.
Sweep element: conic gradient rotating in JS (add to rotators with el sweep, speed 24, dir 1). It's inside orbit, behind rings (z-index order by DOM).
Z-order inside orbit: sweep (z0), crosshair (separate, behind orbit), ticks z1, text z2, ring-outer z3, orbiters z4, core z5.
DOM order in orbit: sweep, ticks, text, ring, orbiters, core.
Tick svg & text svg size: position absolute inset 0 width 100% height 100%.
But ticks circle r=98 within a 200 viewBox maps to nearly full inset — matches the ring-outer at inset 0. Good: outer ring border at 50% radius; tick at 98/100 = 98% — just inside. Sparse ticks r 90.
Orbiter sizes: orb-b inset 11% → radius 39% of orbit → 78% of orbit diameter... wait, orbit container diameter D; orb-b 78% width → radius 0.39D vs core radius 0.15D — fine, no overlap.
Sweep: inset -8%; border-radius 50%; background: conic-gradient(from 0deg, transparent 0deg 300deg, hsl(calc(40+var(--hue)) 95% 66% / 0) 330deg, hsl(calc(40+var(--hue)) 95% 66% / .12) 352deg, transparent 360deg); filter: blur(2px)? Blur on rotating element might be expensive but fine, small. Actually a conic "beam" with a soft edge via multiple stops:
conic-gradient(from 0deg, transparent 0 300deg, gold/.05 330deg, gold/.14 350deg, gold/.05 356deg, transparent 360deg). Rotate via JS.
HUD state dot: .hud-state::before? Include "●" in text; animate opacity blink on the span when active.
Panel title with a small square marker: use "▚" or a CSS dot. Use a CSS ::before square.
Title markup: `The <span class="hollow">Kinetic</span> Circle`? Let me do: "THE KINETIC" line 1 small? Let me design: kicker small, then title 2 lines:
"THE KINETIC" (outline/hollow, Unbounded 700)
"CIRCLE" (solid, 900, gold? or ink)
Big contrast, distinctive. h1:
```html
<h1 class="title"><span class="line1">The Kinetic</span><span class="line2">Circle</span></h1>
```
.line1{ display:block; font: 700 clamp(.8rem,1.6vw,1.05rem) Unbounded; letter-spacing:.42em; text-transform:uppercase; color:var(--mut)? or gold }
.line2{ display:block; font: 900 clamp(2.6rem,7vw,4.6rem) Unbounded; color: ink; } with a gold period? "Circle." with gold dot: .line2::after{content:"."; color:var(--gold)}. Nice signature.
Hmm "The Kinetic" as tracked-out small caps gold; "Circle." giant.
Right description: Space Mono, 12px, line-height 1.7, color mut, text-align right, max-width 36ch. Kbd: border 1px solid line, padding 1px 6px, font 10px, radius 3px.
Header grid: .head{ display:grid; grid-template-columns: 1fr auto; align-items:start; gap: 24px }.
Kicker: font Space Mono 10px ls .3em, gold; a dot before it: ::before content:"" 6px square gold, margin-right.
Now sizing: orbit width: clamp(300px, 62vmin, 620px).
Core 30% → up to 186px, nice.
Text ring legibility: font-size 8.5 units → on 620px: 620/200 = 3.1 → ~26px?? Wait 8.5 * 3.1 = 26.35px — too big! Let me recompute: viewBox 200 maps to 620px → 1 unit = 3.1px. font-size 8.5 units = 26px. Too big. I want ~11px text → font-size 3.6 units. Then char width 0.6*3.6=2.16 + ls 0.8 = 3 * 66 = 198 < 490. Gap again! Circumference is in units 490 = 1520px on screen; ~11px font, 66 chars ≈ 66*7.7px = 508px — need ~175 chars to fill. So either repeat the phrase 2x, or use shorter font... Let me use repeating text: "ORBITAL MOTION · CIRCULAR FORM · SPECIMEN 001 · KINETIC STUDY · " repeated twice (~140 chars) * 7.7 ≈ 1078px, gap 442px (29%). Repeat 3x ≈ 1616 — slight overflow, textPath clips overflow (text past path end doesn't render) — clipping mid-word is ugly.
Options: use font-size 8.5 units (26px) — chunky circular text could actually look great as a design choice (bold Unbounded? Mono 26px on the ring...). Hmm, 26px mono around a 620px circle: circumference 1950px; 66 chars * (0.6*26 + 6ls=21.6) ≈ 66*21.6 ≈ 1425px; gap 525px (27%). Still gap.
Better: tune text length to fit at a chosen size. Target circumference 490 units. With font-size 7 units (21.7px at 620) & ls 2 units: char = 4.2+2 = 6.2 units → chars to fit = 490/6.2 ≈ 79 chars. Write a ~79-char string:
"KINETIC CIRCLE · SPECIMEN Nº 001 · ORBITAL MOTION STUDY · CIRCULAR FORM ·"
Count: KINETIC(7)+1+CIRCLE(6)=14, +3 (" · ")=17, +SPECIMEN(8)=25, +1+2? "Nº 001" =6 → 31, +3=34, ORBITAL MOTION STUDY=18 → 52, +3=55, CIRCULAR FORM=13 → 68, +2(" ·")=70. 70 chars * 6.2 = 434. Gap 56 (11%). Add "· KINETIC"? Push closer: append "MOTION ·" no... add "— 360°" at end: 75 chars → 465, gap 25 (5%) — good enough; the seam looks intentional with a "·".
Actually a simpler robust approach: measure? Overkill. Fine: accept a ~5-10% seam; it's a rotating ring, the seam reads as a gap between words. Good.
font-size 7 units → 21.7px on big screen, ~14px on mobile (340*3.7=...) 340/200=1.7 → 12px. Fine.
Fill: hsl(calc(172 + var(--hue)) 45% 70% / .75). letter-spacing 2. font-family Space Mono, weight 400.
The "Nº" character in SVG — fine with utf8.
Ticks: outer fine: r=99 (just inside the border ring), sw=4.5, pathLength=360, dasharray="0.6 5.4" → 60 ticks. stroke var(--line).
Sparse: r=93, sw=10, dasharray="1.2 38.8" → 9 ticks, stroke hsl(cyan/.30).
Wait dasharray sum must equal 360/n: 60 ticks: total per tick = 6 → 0.6+5.4 ✓. 9 ticks: 40 → 1.2+38.8 ✓.
Ring outer border: 1px solid var(--line); plus a subtle second border offset? Add a ring-outer::after inset -10px? No, keep clean; comet dot: 6px gold with glow, on the border.
Another detail: the gap between ring-outer (inset 0) and ticks r 99 — the ticks overlap with the border; offset ticks to r=96.
OK, also add a "focus" glow behind core: .core-glow covers.
Panel inputs --fill: JS:
```js
function setFill(inp){ const p = (inp.value - inp.min)/(inp.max-inp.min)*100; inp.style.setProperty('--fill', p+'%'); }
```
CSS: input[type=range]{ background: linear-gradient(90deg, var(--gold) var(--fill,50%), hsl(calc(190+var(--hue)) 25% 40% / .3) var(--fill,50%)); }
hue slider track uses cyan → gold gradient? Keep same gold fill; fine.
Buttons: pulseBtn label "◉ Pulse", pauseBtn "❙❙ Hold" / "▶ Resume" swap text.
Pause logic: paused flag; when paused, rotators freeze, sweep freezes, core CSS breathe continues (feels alive) or pause those too? Keep breathe (organic). hudState: "● HELD" coral vs "● ACTIVE" gold.
Also, the vel slider at 0 also freezes rings — that's fine.
Space key: also "P" for pulse? Space is enough.
Let me write the final code. Also make sure no horizontal scrollbar: body overflow hidden.
Mobile layout tweaks:
@media (max-width: 760px){
.head{grid-template-columns:1fr} .head-right{text-align:left; max-width: none; margin-top: 10px}
.line2 font clamp smaller (already clamped)
.row3{flex-direction:column; align-items:stretch; gap:14px}
.panel{width:auto} .hud{justify-content:flex-start}
orbit width: min(82vmin, 520px)
maybe hide crosshairs? Keep, fine.
}
Also small screens, .stage min-height.
Font loading:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Unbounded:wght@400;700;900&display=swap" rel="stylesheet">
Let me write the JS rotators with elements fetched by id/class.
One caveat: setting transform rotate on the .ticks SVG element (an svg) — CSS transform on SVG elements works in modern browsers. Use style.transform = rotate. The SVG has width/height 100% and is positioned; transform-origin defaults to center? In CSS boxes, transform-origin default is 50% 50% — yes for HTML-level svg element. Good.
Orbiters: each .orb div is rotated; the inner i positioned at top:0 left:50%.
Layer parallax: apply in rAF: layer.style.transform = `translate3d(${x}px, ${y}px, 0)`.
Layer depths: outer rings small depth (distant), core larger depth (near) → parallax.
Also add slight overall orbit tilt? No.
Pointermove on window (also deviceorientation? skip).
Touch: pointermove works on drag; fine.
Init starfield on resize — re-init loses positions, acceptable.
Spark color: use HSL strings built from current HUE at spawn.
Let me count z-index and pointer: canvas z 0; body::before/::after z 1 pointer none; .frame z 2 pointer none; interactive children pointer auto.
Stage: covers the middle row; but pointerdown attached to window with guard — stage isn't strictly needed for events but I keep the crosshair cursor on stage.
Guard: if(e.target.closest('.panel, .hud, header, input, button')) return; else pulse(). Note: core button → closest('button') → excluded from window handler, but core has its own click handler → pulse. pulseBtn click → pulse. Good, no double.
Actually clicking on kbd in header → header excluded → no pulse. Fine.
Click anywhere on empty space pulses — including over text. Good.
Also add cursor crosshair to stage: .stage{cursor:crosshair}.
Let me write it all out.
Also `em`/kbd details.
Title text: "The Kinetic" / "Circle." with gold dot.
Kicker: "SPECIMEN Nº 001 · CIRCULAR FORM".
Description: "A living study in orbital motion — seven rings, one breathing core, and a starfield that answers. Drag the dials, move the cursor, fire a pulse."
Hint: "[ click ] or [ space ] → fire a pulse". kbd styled.
Panel: "CONTROL DIAL" title; VELOCITY; SPECTRUM; buttons Pulse / Hold.
HUD: ORBIT θ, DRIFT, PULSES, STATE.
Let me write the code now, being careful about syntax. CSS hsl with calc + var: `hsl(calc(40 + var(--hue)) 95% 66%)` — valid modern syntax (space-separated). With alpha: `hsl(calc(40 + var(--hue)) 95% 66% / .5)`.
--hue is unitless number set via JS setProperty('--hue', v) — calc(40 + 0) works, since calc with numbers for hue: `calc(40 + var(--hue))` both numbers → number, valid for hue (hue accepts number). Yes.
Default --hue: 0.
Also, background gradients on body that reference --hue — body background re-paints on hue change; fine (only during slider drag).
Perf: fine.
Let me write the stars init:
```js
function initStars(){
stars = [];
const n = Math.min(240, Math.round(W*H/9000));
for(let i=0;i<n;i++){
stars.push({ x: Math.random()*W, y: Math.random()*H, r: .3 + Math.random()*1.3, a: .15 + Math.random()*.55, p: Math.random()*Math.PI*2, s: .4 + Math.random()*1.6, vx: (Math.random()-.5)*4, vy: (Math.random()-.5)*4, hue: Math.random()<.12 ? 1 : 0 });
}
dust = [];
for(let i=0;i<14;i++) dust.push({x:Math.random()*W, y:Math.random()*H, r:1.6+Math.random()*1.6, a:.05+Math.random()*.08, vy:-(4+Math.random()*8), vx:(Math.random()-.5)*3});
}
```
Star color: hue? use hsl(172+HUE, 70%, 80%) : 'rgba(220,235,245,1)' with globalAlpha.
Draw:
```js
ctx.clearRect(0,0,W,H);
for(const st of stars){
const tw = RM ? 1 : (0.55 + 0.45*Math.sin(tGlobal*st.s + st.p));
ctx.globalAlpha = st.a * tw;
ctx.fillStyle = st.hue ? hslC(172, 70, 80) : '#dceaf2';
ctx.beginPath(); ctx.arc(st.x, st.y, st.r, 0, 7); ctx.fill();
if(!paused){ st.x += st.vx*dt; st.y += st.vy*dt; } // drift also paused? keep drift always? tie to pause for consistency: if(!paused)
wrap: st.x=(st.x+W)%W etc (careful with negative: ((x+W)%W+W)%W)
}
```
Dust similarly, wrap y.
hslC(h,s,l) => `hsl(${(h+HUE)%360} ${s}% ${l}%)`.
Shocks drawn after DOM? Canvas is behind DOM — shock rings will be hidden behind orbit rings/core? The core is opaque in the center; shock starts at core radius so it's just outside the core — visible. It passes behind the ring strokes — fine, actually nice layering.
But sparks flying outward are also behind rings — fine.
Shock max: Math.hypot(W,H)/2.
Pulse uses core radius: const rect = orbit.getBoundingClientRect(); coreR = rect.width*0.15; center = orbit center in canvas coords (canvas covers viewport, so center = rect.left + rect.width/2 ...). Store on each pulse: cx, cy.
Initial welcome pulse: after 700ms.
Core click: coreBtn.addEventListener('click', pulse) — also pointerdown on window is excluded for buttons, good.
Pause button toggle:
```js
pauseBtn.onclick = () => { paused = !paused; pauseBtn.setAttribute('aria-pressed', paused); pauseBtn.innerHTML = paused ? '▶ Resume' : '❙❙ Hold'; hudState.textContent = paused ? '● HELD' : '● ACTIVE'; hudState.classList.toggle('held', paused); };
```
Velocity:
```js
velInput.addEventListener('input', e => { velMult = e.target.value/100; outVel.textContent = e.target.value + '%'; hudVel.textContent = e.target.value + '%'; setFill(e.target); });
```
Hue similar → HUE = +value; setProperty.
Angle HUD: deg = ((rotators[0].a % 360)+360)%360 → `String(Math.round(deg)).padStart(3,'0')+'°'`.
rAF loop with all updates.
RM: reduce star drift/twinkle amplitude, and default vel slider? Keep user control; just tone down.
Edge: if tab hidden, rAF pauses — dt clamped, fine.
Let me finalize sizes/spacing and write the file. Also add ::selection and focus-visible.
Panel width 240px; on desktop, row 3: panel left, hud right.
HUD display flex gap 22px; span{display:flex; gap:8px; align-items:baseline} label muted, b ink.
Let me write. Also ensure `<html lang="en">`.
One more check — .orbit layers: `.layer{position:absolute; inset:0; will-change:transform;}` children fill the layer.
Orbiter i:
```css
.orb{position:absolute; border-radius:50%;}
.orb i{position:absolute; top:0; left:50%; width:7px; height:7px; border-radius:50%; transform:translate(-50%,-50%); background:currentColor; box-shadow:0 0 12px 2px currentColor;}
.orb-a{ inset:-7%; color: var(--gold); animation:none }
.orb-b{ inset:12%; color: var(--cyan); } .orb-b i{width:5px;height:5px}
.orb-c{ inset:-16%; color: var(--coral); } .orb-c i{width:4px;height:4px}
```
inset with negative values OK.
Wait orb-c at -16% is bigger than orbit — sweep is also -8%... fine.
z-index: .layer order by DOM: sweep, ticks, text, ring, orbiters, core. Later = on top. Good, no z-index needed.
Sweep inset:-10%.
Core layer: contains centered button: .layer is inset 0, .core { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%) ... } — but the breathe animation will animate transform scale → conflict with translate. Solution: use inset margin auto on core with fixed width 30% height 30%:
.core{ position:absolute; inset:0; margin:auto; width:30%; height:30%; } and animation: breathe → transform: scale keyframes only — since transform replaces, the centering uses inset/margin not transform.
@keyframes breathe{ 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.05) } }
Flare: filter only.
.core-glow: position absolute; inset:-35%; border-radius 50%; background: radial-gradient(circle, hsl(gold/.55), hsl(gold/.12) 45%, transparent 70%);
.core-surface: inset:10%; border-radius:50%; conic; animation spin 18s linear infinite; opacity:.5; mix-blend-mode: screen;
.core-ring: inset:5.5%; border-radius:50%; border:1px solid rgba(255,255,255,.35);
Add a smaller offset highlight: .core::after { content:""; position:absolute; top:16%; left:22%; width:26%; height:26%; border-radius:50%; background: radial-gradient(circle, rgba(255,255,255,.9), transparent 70%); filter: blur(2px); } — a specular.
Button base: border:none (use a ring span instead); background: radial-gradient(circle at 35% 32%, #fff6e6 0%, var(--gold) 30%, var(--coral) 68%, hsl(calc(8+var(--hue)) 65% 24%) 100%);
box-shadow: 0 0 40px hsl(calc(40+var(--hue)) 95% 60% / .5), 0 0 120px hsl(calc(40+var(--hue)) 95% 60% / .25);
Focus-visible: outline: 2px dashed var(--cyan); outline-offset: 6px.
Cursor pointer.
OK, let me also add small "corner" frame marks on .frame for instrument aesthetic: 4 corner brackets. Do via .frame::before and .frame::after? Need 4 corners → use 2 pseudo elements on .frame (top-left/bottom-right) and another 2 on .stage (bottom-left/top-right)? Simpler: only .frame::before/::after for TL and BR:
```css
.frame::before{content:""; position:absolute; top:0; left:0; width:14px; height:14px; border-top:1px solid var(--line); border-left:1px solid var(--line);}
```
Hmm, padding is clamp — corners are at the padding edges. Use offset 10px. Add a bit of decoration. Actually, let me skip frame corners — panel already has corner accents and crosshairs give instrument feel. Keep clean.
Let me write the final answer: short intro sentence + a single HTML file. The response is mostly the code.
Let me now write the complete file carefully.
Also: kbd styling inside .hint.
Final check on text ring seam: fine.
Slight opacity animation on comet? No.
Let me write the code.
[End thinking]
Here's a fully self-contained piece — a living orbital instrument: layered rotating rings, a breathing core, a reactive starfield, parallax on cursor movement, and dials to control velocity and spectrum. Click anywhere (or hit space) to fire a pulse.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The Kinetic Circle — Specimen Nº 001</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Unbounded:wght@400;700;900&display=swap" rel="stylesheet">
<style>
:root{
--hue: 0;
--bg: #0a141b;
--ink: #e4f1f5;
--mut: #6f8d99;
--gold: hsl(calc(40 + var(--hue)) 95% 66%);
--cyan: hsl(calc(172 + var(--hue)) 75% 62%);
--coral: hsl(calc(8 + var(--hue)) 90% 64%);
--line: hsl(calc(190 + var(--hue)) 35% 50% / .22);
}
*{ margin:0; padding:0; box-sizing:border-box; }
html, body{ height:100%; }
body{
background:
radial-gradient(1100px 760px at 50% 42%, hsl(calc(172 + var(--hue)) 45% 13% / .6), transparent 62%),
radial-gradient(900px 700px at 80% 86%, hsl(calc(40 + var(--hue)) 50% 12% / .45), transparent 60%),
var(--bg);
color: var(--ink);
font-family: 'Space Mono', monospace;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
::selection{ background: var(--gold); color: var(--bg); }
canvas#sky{ position:fixed; inset:0; z-index:0; }
body::before{ /* vignette */
content:""; position:fixed; inset:0; z-index:1; pointer-events:none;
background: radial-gradient(125% 125% at 50% 44%, transparent 52%, rgba(3,9,13,.8) 100%);
}
body::after{ /* grain */
content:""; position:fixed; inset:0; z-index:1; pointer-events:none; opacity:.045;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}
.frame{
position:fixed; inset:0; z-index:2;
display:grid; grid-template-rows:auto 1fr auto;
padding:clamp(18px, 3.4vw, 44px); gap:10px;
pointer-events:none;
}
.frame *{ pointer-events:none; }
.panel, .hud, .btn, input{ pointer-events:auto; }
/* ── header ─────────────────────────────── */
.head{ display:grid; grid-template-columns:1fr auto; align-items:start; gap:28px; }
.kicker{
font-size:10px; letter-spacing:.32em; color:var(--gold); text-transform:uppercase;
display:flex; align-items:center; gap:10px; margin-bottom:12px;
}
.kicker::before{ content:""; width:7px; height:7px; background:var(--gold); flex:none; }
.title .line1{
display:block; font-family:'Unbounded', sans-serif; font-weight:700;
font-size:clamp(.72rem, 1.5vw, 1rem); letter-spacing:.46em; text-transform:uppercase;
color:var(--mut); margin-bottom:4px;
}
.title .line2{
display:block; font-family:'Unbounded', sans-serif; font-weight:900;
font-size:clamp(2.5rem, 7vw, 4.6rem); line-height:1; letter-spacing:.01em; color:var(--ink);
}
.title .line2::after{ content:"."; color:var(--gold); }
.head-right{ max-width:36ch; text-align:right; font-size:12px; line-height:1.75; color:var(--mut); }
.hint{ margin-top:10px; font-size:11px; letter-spacing:.08em; }
kbd{
font-family:inherit; font-size:10px; padding:2px 7px;
border:1px solid var(--line); border-radius:3px; color:var(--ink);
}
/* ── stage & orbit ──────────────────────── */
.stage{ position:relative; display:grid; place-items:center; cursor:crosshair; }
.cross{ position:absolute; background:linear-gradient(90deg, transparent, var(--line) 18% 82%, transparent); }
.cross-h{ left:0; right:0; top:50%; height:1px; }
.cross-v{ top:0; bottom:0; left:50%; width:1px; background:linear-gradient(180deg, transparent, var(--line) 18% 82%, transparent); }
.orbit{ position:relative; width:min(66vmin, 620px); min-width:280px; aspect-ratio:1; }
.layer{ position:absolute; inset:0; will-change:transform; }
.sweep{
position:absolute; inset:-10%; border-radius:50%; will-change:transform;
background:conic-gradient(from 0deg,
transparent 0deg 300deg,
hsl(calc(40 + var(--hue)) 95% 66% / 0) 318deg,
hsl(calc(40 + var(--hue)) 95% 66% / .13) 350deg,
transparent 360deg);
}
.ticks, .text-ring{ position:absolute; inset:0; width:100%; height:100%; overflow:visible; will-change:transform; }
.ring-outer{ position:absolute; inset:0; border:1px solid var(--line); border-radius:50%; will-change:transform; }
.comet{
position:absolute; top:0; left:50%; width:7px; height:7px; border-radius:50%;
transform:translate(-50%,-50%); background:var(--gold);
box-shadow:0 0 12px 3px hsl(calc(40 + var(--hue)) 95% 66% / .8);
}
.orb{ position:absolute; border-radius:50%; will-change:transform; }
.orb i{
position:absolute; top:0; left:50%; border-radius:50%; transform:translate(-50%,-50%);
width:7px; height:7px; background:currentColor; box-shadow:0 0 12px 2px currentColor;
}
.orb-a{ inset:-7%; color:var(--gold); }
.orb-b{ inset:12%; color:var(--cyan); } .orb-b i{ width:5px; height:5px; }
.orb-c{ inset:-16%; color:var(--coral); } .orb-c i{ width:4px; height:4px; }
/* the core */
.core{
position:absolute; inset:0; margin:auto; width:30%; height:30%;
border:none; border-radius:50%; cursor:pointer; appearance:none;
background:radial-gradient(circle at 35% 32%, #fff6e6 0%, var(--gold) 32%, var(--coral) 68%, hsl(calc(8 + var(--hue)) 65% 24%) 100%);
box-shadow:
0 0 44px hsl(calc(40 + var(--hue)) 95% 60% / .5),
0 0 130px hsl(calc(40 + var(--hue)) 95% 60% / .22);
transition:filter .45s ease;
animation:breathe 4.6s ease-in-out infinite;
}
.core:focus-visible{ outline:2px dashed var(--cyan); outline-offset:8px; }
.core.flare{ filter:brightness(1.55) saturate(1.35); }
.core-glow{
position:absolute; inset:-38%; border-radius:50%;
background:radial-gradient(circle, hsl(calc(40 + var(--hue)) 95% 66% / .5), hsl(calc(40 + var(--hue)) 95% 66% / .1) 46%, transparent 72%);
animation:glowPulse 4.6s ease-in-out infinite; transition:transform .5s ease;
}
.core.flare .core-glow{ transform:scale(1.3); }
.core-surface{
position:absolute; inset:10%; border-radius:50%; opacity:.45; mix-blend-mode:screen;
background:conic-gradient(from 0deg,
transparent 0deg 40deg, hsl(calc(40 + var(--hue)) 95% 75% / .5) 100deg,
transparent 170deg, hsl(calc(8 + var(--hue)) 90% 70% / .4) 260deg, transparent 320deg);
animation:spin 18s linear infinite;
}
.core-ring{ position:absolute; inset:5.5%; border-radius:50%; border:1px solid rgba(255,255,255,.35); }
.core::after{
content:""; position:absolute; top:15%; left:22%; width:27%; height:27%; border-radius:50%;
background:radial-gradient(circle, rgba(255,255,255,.95), transparent 70%); filter:blur(2px);
}
@keyframes breathe{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.05); } }
@keyframes glowPulse{ 0%,100%{ opacity:.7; } 50%{ opacity:1; } }
@keyframes spin{ to{ transform:rotate(360deg); } }
/* ── bottom row: panel + hud ────────────── */
.row3{ display:flex; justify-content:space-between; align-items:flex-end; gap:24px; }
.panel{
width:242px; padding:16px 18px 18px; position:relative;
background:hsl(calc(200 + var(--hue)) 35% 8% / .72);
border:1px solid var(--line);
}
.panel::before{
content:""; position:absolute; top:-1px; left:-1px; width:11px; height:11px;
border-top:2px solid var(--gold); border-left:2px solid var(--gold);
}
.panel-title{
font-family:'Unbounded', sans-serif; font-weight:700; font-size:10px;
letter-spacing:.26em; color:var(--mut); margin-bottom:16px;
}
.ctl{ display:block; margin-bottom:16px; }
.ctl-label{
display:flex; justify-content:space-between; font-size:10px;
letter-spacing:.18em; color:var(--mut); margin-bottom:8px;
}
.ctl-label output{ color:var(--gold); letter-spacing:.05em; }
input[type=range]{
-webkit-appearance:none; appearance:none; width:100%; height:2px; display:block; cursor:ew-resize;
background:linear-gradient(90deg, var(--gold) var(--fill,33%), hsl(calc(190 + var(--hue)) 25% 45% / .3) var(--fill,33%));
}
input[type=range]::-webkit-slider-thumb{
-webkit-appearance:none; width:13px; height:13px; border-radius:50%;
background:var(--bg); border:2px solid var(--gold); transition:background .2s;
}
input[type=range]::-webkit-slider-thumb:hover{ background:var(--gold); }
input[type=range]::-moz-range-thumb{
width:11px; height:11px; border-radius:50%; background:var(--bg); border:2px solid var(--gold);
}
input[type=range]:focus-visible{ outline:2px dashed var(--cyan); outline-offset:6px; }
.btns{ display:flex; gap:8px; }
.btn{
font-family:'Space Mono', monospace; font-size:11px; letter-spacing:.1em;
padding:9px 13px; background:transparent; color:var(--ink);
border:1px solid var(--line); cursor:pointer;
transition:border-color .2s, color .2s, background .2s, transform .1s;
}
.btn:hover{ border-color:var(--gold); color:var(--gold); }
.btn:active{ transform:translateY(1px); }
.btn[aria-pressed="true"]{ background:var(--gold); border-color:var(--gold); color:var(--bg); }
.btn:focus-visible{ outline:2px dashed var(--cyan); outline-offset:3px; }
.hud{ display:flex; gap:26px; font-size:11px; letter-spacing:.12em; color:var(--mut); }
.hud span{ display:flex; gap:9px; align-items:baseline; white-space:nowrap; }
.hud b{ font-weight:400; color:var(--ink); }
#hudState{ color:var(--gold); animation:blink 2.2s ease-in-out infinite; }
#hudState.held{ color:var(--coral); animation:none; }
@keyframes blink{ 50%{ opacity:.45; } }
@media (max-width: 760px){
.head{ grid-template-columns:1fr; gap:14px; }
.head-right{ text-align:left; max-width:none; }
.orbit{ width:min(80vmin, 480px); }
.row3{ flex-direction:column; align-items:stretch; gap:14px; }
.panel{ width:auto; }
.hud{ justify-content:flex-start; flex-wrap:wrap; gap:14px 22px; }
}
@media (prefers-reduced-motion: reduce){
.core, .core-glow, .core-surface, #hudState{ animation:none; }
}
</style>
</head>
<body>
<canvas id="sky"></canvas>
<div class="frame">
<header class="head">
<div>
<p class="kicker">Specimen Nº 001 · Circular Form</p>
<h1 class="title">
<span class="line1">The Kinetic</span>
<span class="line2">Circle</span>
</h1>
</div>
<div class="head-right">
<p>A living study in orbital motion — five rings turning at their own tempos, a core that breathes, and a starfield that answers back. Steer the dials, drift the cursor, fire a pulse.</p>
<p class="hint"><kbd>click</kbd> / <kbd>space</kbd> &nbsp;→&nbsp; fire a pulse</p>
</div>
</header>
<div class="stage" id="stage">
<div class="cross cross-h"></div>
<div class="cross cross-v"></div>
<div class="orbit" id="orbit">
<div class="layer" data-depth="8"><div class="sweep" id="sweep"></div></div>
<div class="layer" data-depth="10">
<svg class="ticks" id="ticks" viewBox="0 0 200 200" aria-hidden="true">
<circle cx="100" cy="100" r="99" fill="none" pathLength="360"
stroke="var(--line)" stroke-width="4.5" stroke-dasharray="0.6 5.4" opacity=".8"/>
<circle cx="100" cy="100" r="91" fill="none" pathLength="360"
stroke="hsl(calc(172 + var(--hue)) 75% 62% / .35)" stroke-width="10"
stroke-dasharray="1.2 38.8"/>
</svg>
</div>
<div class="layer" data-depth="16">
<svg class="text-ring" id="textRing" viewBox="0 0 200 200" aria-hidden="true">
<defs><path id="tp" d="M100,100 m-78,0 a78,78 0 1,1 156,0 a78,78 0 1,1 -156,0"/></defs>
<text fill="hsl(calc(172 + var(--hue)) 45% 70% / .8)"
style="font-family:'Space Mono',monospace; font-size:7px; letter-spacing:2px;">
<textPath href="#tp">KINETIC CIRCLE · SPECIMEN Nº 001 · ORBITAL MOTION STUDY · CIRCULAR FORM — 360° · </textPath>
</text>
</svg>
</div>
<div class="layer" data-depth="12"><div class="ring-outer" id="ringOuter"><span class="comet"></span></div></div>
<div class="layer" data-depth="20">
<div class="orb orb-a" id="orbA"><i></i></div>
<div class="orb orb-b" id="orbB"><i></i></div>
<div class="orb orb-c" id="orbC"><i></i></div>
</div>
<div class="layer" data-depth="26">
<button class="core" id="core" aria-label="Fire a pulse">
<span class="core-glow"></span>
<span class="core-surface"></span>
<span class="core-ring"></span>
</button>
</div>
</div>
</div>
<div class="row3">
<aside class="panel">
<p class="panel-title">CONTROL DIAL</p>
<label class="ctl">
<span class="ctl-label">VELOCITY <output id="outVel">100%</output></span>
<input type="range" id="vel" min="0" max="300" value="100" aria-label="Orbit velocity">
</label>
<label class="ctl">
<span class="ctl-label">SPECTRUM <output id="outHue">0°</output></span>
<input type="range" id="hue" min="0" max="360" value="0" aria-label="Colour spectrum shift">
</label>
<div class="btns">
<button class="btn" id="pulseBtn">◉ Pulse</button>
<button class="btn" id="pauseBtn" aria-pressed="false">❙❙ Hold</button>
</div>
</aside>
<footer class="hud">
<span>ORBIT θ <b id="hudAngle">000°</b></span>
<span>DRIFT <b id="hudVel">100%</b></span>
<span>PULSES <b id="hudPulse">0</b></span>
<span id="hudState">● ACTIVE</span>
</footer>
</div>
</div>
<script>
(() => {
const $ = s => document.querySelector(s);
const RM = matchMedia('(prefers-reduced-motion: reduce)').matches;
const canvas = $('#sky'), ctx = canvas.getContext('2d');
const orbit = $('#orbit'), core = $('#core');
const layers = [...document.querySelectorAll('.layer')];
let W, H, HUE = 0, velMult = 1, paused = false, pulses = 0;
/* ── canvas: stars, dust, sparks, shockwaves ── */
let stars = [], dust = [], sparks = [], shocks = [];
const hsl = (h, s, l, a = 1) => `hsla(${(h + HUE + 360) % 360}, ${s}%, ${l}%, ${a})`;
function resize() {
const d = Math.min(devicePixelRatio || 1, 2);
W = innerWidth; H = innerHeight;
canvas.width = W * d; canvas.height = H * d;
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
ctx.setTransform(d, 0, 0, d, 0, 0);
seed();
}
function seed() {
const n = Math.min(230, Math.round(W * H / 9000));
stars = Array.from({ length: n }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: .3 + Math.random() * 1.3, a: .15 + Math.random() * .55,
p: Math.random() * 6.28, s: .4 + Math.random() * 1.6,
vx: (Math.random() - .5) * 4, vy: (Math.random() - .5) * 4,
tint: Math.random() < .12
}));
dust = Array.from({ length: 14 }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: 1.6 + Math.random() * 1.8, a: .04 + Math.random() * .08,
vx: (Math.random() - .5) * 3, vy: -(3 + Math.random() * 8)
}));
}
function pulse() {
pulses++; $('#hudPulse').textContent = pulses;
const r = orbit.getBoundingClientRect();
const cx = r.left + r.width / 2, cy = r.top + r.height / 2, cr = r.width * .15;
shocks.push({ x: cx, y: cy, r: cr * 1.04, a: .85, w: 2, s: 640 });
shocks.push({ x: cx, y: cy, r: cr * .98, a: .5, w: 1, s: 900 });
for (let i = 0; i < 26; i++) {
const ang = Math.random() * 6.28, sp = 120 + Math.random() * 330;
sparks.push({
x: cx, y: cy,
vx: Math.cos(ang) * sp, vy: Math.sin(ang) * sp,
life: .5 + Math.random() * .8, max: 1.3,
c: [40, 172, 8][i % 3]
});
}
core.classList.add('flare');
clearTimeout(core._ft);
core._ft = setTimeout(() => core.classList.remove('flare'), 620);
}
/* ── ring rotators (JS-driven so dials control everything) ── */
const rot = [
{ el: $('#sweep'), spd: 24, dir: 1, a: 0 },
{ el: $('#ringOuter'), spd: 12, dir: 1, a: 0 },
{ el: $('#ticks'), spd: 6, dir: -1, a: 0 },
{ el: $('#textRing'), spd: 4, dir: 1, a: 0 },
{ el: $('#orbA'), spd: 42, dir: 1, a: 20 },
{ el: $('#orbB'), spd: 27, dir: -1, a: 120 },
{ el: $('#orbC'), spd: 16, dir: 1, a: 260 }
];
/* ── parallax ── */
let tx = 0, ty = 0, px = 0, py = 0;
addEventListener('pointermove', e => {
tx = (e.clientX / W - .5) * 2;
ty = (e.clientY / H - .5) * 2;
});
/* ── main loop ── */
let last = performance.now(), t = 0;
function frame(now) {
const dt = Math.min((now - last) / 1000, .05); last = now; t += dt;
if (!paused) for (const o of rot) { o.a = (o.a + o.spd * o.dir * velMult * dt) % 360; o.el.style.transform = `rotate(${o.a}deg)`; }
$('#hudAngle').textContent = String(Math.round((rot[0].a % 360 + 360) % 360)).padStart(3, '0') + '°';
px += (tx - px) * Math.min(1, dt * 4); py += (ty - py) * Math.min(1, dt * 4);
for (const l of layers) l.style.transform = `translate3d(${px * l.dataset.depth}px, ${py * l.dataset.depth}px, 0)`;
// sky
ctx.clearRect(0, 0, W, H);
const amp = RM ? .05 : 1;
for (const s of stars) {
if (!paused) { s.x = (s.x + s.vx * dt + W) % W; s.y = (s.y + s.vy * dt + H) % H; }
ctx.globalAlpha = s.a * (1 - amp * .45 + amp * .45 * Math.sin(t * s.s + s.p));
ctx.fillStyle = s.tint ? hsl(172, 70, 80) : '#dceaf2';
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.fill();
}
for (const d of dust) {
if (!paused) { d.x = (d.x + d.vx * dt + W) % W; d.y = (d.y + d.vy * dt + H) % H; }
ctx.globalAlpha = d.a; ctx.fillStyle = hsl(172, 60, 75);
ctx.beginPath(); ctx.arc(d.x, d.y, d.r, 0, 7); ctx.fill();
}
for (let i = sparks.length - 1; i >= 0; i--) {
const p = sparks[i];
if (!paused) { p.x += p.vx * dt; p.y += p.vy * dt; p.vx *= .985; p.vy *= .985; }
p.life -= dt;
if (p.life <= 0) { sparks.splice(i, 1); continue; }
ctx.globalAlpha = Math.max(0, p.life / p.max);
ctx.fillStyle = hsl(p.c, 90, 66);
ctx.beginPath(); ctx.arc(p.x, p.y, 1.8, 0, 7); ctx.fill();
}
const maxR = Math.hypot(W, H) / 2;
for (let i = shocks.length - 1; i >= 0; i--) {
const s = shocks[i];
if (!paused) s.r += s.s * dt * (.55 + velMult * .55);
s.a -= dt * 1.05;
if (s.a <= 0 || s.r > maxR) { shocks.splice(i, 1); continue; }
ctx.globalAlpha = 1;
ctx.lineWidth = s.w; ctx.strokeStyle = hsl(40, 95, 66, s.a);
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, 7); ctx.stroke();
}
ctx.globalAlpha = 1;
requestAnimationFrame(frame);
}
/* ── interactions ── */
addEventListener('pointerdown', e => {
if (e.target.closest('.panel, input, button, .hud, .btn')) return;
pulse();
});
core.addEventListener('click', pulse);
$('#pulseBtn').addEventListener('click', pulse);
addEventListener('keydown', e => {
if (e.code === 'Space' && !/INPUT|BUTTON|TEXTAREA/.test(e.target.tagName)) {
e.preventDefault(); if (!e.repeat) pulse();
}
});
$('#pauseBtn').addEventListener('click', e => {
paused = !paused;
e.currentTarget.setAttribute('aria-pressed', paused);
e.currentTarget.textContent = paused ? '▶ Resume' : '❙❙ Hold';
const st = $('#hudState');
st.textContent = paused ? '● HELD' : '● ACTIVE';
st.classList.toggle('held', paused);
});
const fill = el => el.style.setProperty('--fill', (el.value - el.min) / (el.max - el.min) * 100 + '%');
$('#vel').addEventListener('input', e => {
velMult = e.target.value / 100;
$('#outVel').textContent = $('#hudVel').textContent = e.target.value + '%';
fill(e.target);
});
$('#hue').addEventListener('input', e => {
HUE = +e.target.value;
document.documentElement.style.setProperty('--hue', HUE);
$('#outHue').textContent = HUE + '°';
fill(e.target);
});
addEventListener('resize', resize);
resize();
fill($('#vel')); fill($('#hue'));
requestAnimationFrame(frame);
setTimeout(pulse, 700); // welcome flare
})();
</SCRIPT_ESCAPE>
</body>
</html>
```
**What's in it**
- **Five independent orbits** — a radar sweep, a 60-tick dial, rotating circular type, an outer ring with a comet, and three satellites in gold / cyan / coral, all driven from one loop so the velocity dial genuinely governs them.
- **A breathing core** — layered radial + conic gradients, a specular highlight, a slow surface swirl, and a flare when you pulse.
- **Canvas starfield** with twinkle, drift, rising dust, plus shockwave rings and spark bursts on every pulse.
- **Parallax depth** — each ring layer shifts at its own rate as your cursor moves (lerped for smoothness).
- **Spectrum dial** recolors the entire scene live, **Hold** freezes motion, and the HUD reports orbit angle, drift, and pulse count in real time.
Drop it in a file and open it — it also respects `prefers-reduced-motion`.
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ORB-01 · Kinetic Circle</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Unbounded:wght@300;500;800&family=Space+Mono:wght@400;700&display=swap"
rel="stylesheet">
<style>
:root {
--bg0: #07161b;
--bg1: #0d2229;
--ink: #e8f4f4;
--dim: #7fa3a8;
--faint: #33505a;
--gold: #f5b942;
--coral: #ff6b5e;
--ice: #8fd8e8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box
}
html,
body {
height: 100%
}
body {
background: radial-gradient(120% 90% at 50% 38%, var(--bg1) 0%, var(--bg0) 62%, #04090c 100%);
color: var(--ink);
font-family: 'Space Mono', monospace;
overflow: hidden;
user-select: none;
}
::selection {
background: var(--gold);
color: #04141a
}
/* ---- ambient layers ---- */
.bg-rings {
position: fixed;
inset: -40%;
pointer-events: none;
opacity: .5
}
.bg-rings span {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
border: 1px dashed var(--faint);
border-radius: 50%;
}
.grain {
position: fixed;
inset: 0;
pointer-events: none;
opacity: .07;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.motes span {
position: fixed;
border-radius: 50%;
pointer-events: none;
animation: drift linear infinite;
}
@keyframes drift {
0% {
transform: translateY(12vh) scale(.6);
opacity: 0
}
12% {
opacity: .9
}
88% {
opacity: .9
}
100% {
transform: translateY(-115vh) scale(1.1);
opacity: 0
}
}
/* ---- stage ---- */
.stage {
position: fixed;
inset: 0;
display: grid;
place-items: center;
perspective: 1100px
}
.orb {
position: relative;
width: min(74vmin, 620px);
aspect-ratio: 1;
transform-style: preserve-3d;
will-change: transform;
cursor: crosshair;
}
.orb svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
display: block
}
.spin {
transform-box: fill-box;
transform-origin: center
}
@keyframes rot {
to {
transform: rotate(360deg)
}
}
@keyframes rotR {
to {
transform: rotate(-360deg)
}
}
.r1 {
animation: rot 70s linear infinite
}
.r2 {
animation: rotR 26s linear infinite
}
.r3 {
animation: rot 44s linear infinite
}
.r4 {
animation: rotR 18s linear infinite
}
.r5 {
animation: rot 32s linear infinite
}
.s1 {
animation: rot 12s linear infinite
}
.s2 {
animation: rotR 20s linear infinite
}
.s3 {
animation: rot 30s linear infinite
}
.orb.paused * {
animation-play-state: paused
}
.orb svg * {
transition: stroke .5s, fill .5s
}
.orb::before {
content: "";
position: absolute;
inset: -30%;
border-radius: 50%;
background: radial-gradient(circle, rgba(143, 216, 232, .14), transparent 62%);
opacity: 0;
transition: opacity .6s;
pointer-events: none;
}
.orb:hover::before {
opacity: 1
}
.orb:hover .r2 {
animation-duration: 9s
}
.orb:hover .r4 {
animation-duration: 6s
}
.orb:hover .s1 {
animation-duration: 5s
}
.orb:hover #core {
animation-duration: 1.5s
}
#core {
transform-box: fill-box;
transform-origin: center;
animation: breathe 3.4s ease-in-out infinite
}
@keyframes breathe {
0%,
100% {
transform: scale(1)
}
50% {
transform: scale(1.05)
}
}
.ripple {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
border: 2px solid var(--ice);
border-radius: 50%;
pointer-events: none;
}
@keyframes rip {
to {
width: 160%;
height: 160%;
opacity: 0;
border-width: 1px
}
}
@keyframes flash {
to {
transform: scale(1.35);
opacity: .9
}
}
#core.flash {
animation: flash .45s ease-out
}
/* ---- HUD ---- */
.hud {
position: fixed;
pointer-events: none;
z-index: 5
}
.hud-tl {
top: 28px;
left: 32px
}
.hud-tr {
top: 28px;
right: 32px;
text-align: right
}
.hud-bl {
bottom: 26px;
left: 32px
}
.hud-br {
bottom: 26px;
right: 32px;
text-align: right
}
.tag {
display: inline-flex;
align-items: center;
gap: 9px;
font-size: 10px;
letter-spacing: .32em;
text-transform: uppercase;
color: var(--dim);
}
.tag::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--gold);
animation: blink 2.2s ease-in-out infinite
}
@keyframes blink {
50% {
opacity: .25
}
}
.display {
font-family: 'Unbounded', sans-serif;
font-weight: 800;
font-size: clamp(1.15rem, 2.6vw, 1.9rem);
letter-spacing: .14em;
margin-top: 10px;
}
.display em {
font-style: normal;
font-weight: 300;
color: var(--gold)
}
.readout {
font-size: 10.5px;
line-height: 1.9;
color: var(--dim);
letter-spacing: .08em
}
.readout b {
color: var(--ink);
font-weight: 400
}
.readout .dot {
color: var(--coral)
}
.bl-line {
font-family: 'Unbounded', sans-serif;
font-weight: 300;
font-size: clamp(.8rem, 1.5vw, 1rem);
letter-spacing: .04em;
margin-bottom: 10px;
}
.bl-line b {
font-weight: 500;
color: var(--ice)
}
.hint {
font-size: 10px;
letter-spacing: .14em;
color: var(--faint)
}
.hint kbd {
border: 1px solid var(--faint);
border-radius: 3px;
padding: 1px 5px;
color: var(--dim);
}
.controls {
pointer-events: auto;
display: flex;
gap: 10px;
justify-content: flex-end;
margin-bottom: 14px
}
.controls button {
font-family: 'Space Mono', monospace;
font-size: 10px;
letter-spacing: .2em;
background: transparent;
border: 1px solid var(--faint);
color: var(--dim);
padding: 8px 14px;
border-radius: 999px;
cursor: pointer;
transition: all .25s;
}
.controls button:hover {
border-color: var(--gold);
color: var(--gold);
transform: translateY(-2px)
}
.controls button:focus-visible {
outline: 2px solid var(--gold);
outline-offset: 3px
}
.speed {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
font-size: 9px;
letter-spacing: .3em;
color: var(--faint)
}
.speed input {
-webkit-appearance: none;
appearance: none;
width: 120px;
height: 2px;
background: var(--faint);
outline: none;
cursor: pointer;
}
.speed input::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--gold);
border: 2px solid var(--bg0);
transition: transform .2s;
}
.speed input::-webkit-slider-thumb:hover {
transform: scale(1.35)
}
.speed input::-moz-range-thumb {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--gold);
border: 2px solid var(--bg0);
}
.speed b {
color: var(--gold);
font-weight: 400;
min-width: 4ch
}
.mark {
position: fixed;
width: 18px;
height: 18px;
pointer-events: none;
z-index: 4;
opacity: .45;
animation: cornerIn 1s .3s backwards
}
.mark::before,
.mark::after {
content: "";
position: absolute;
background: var(--faint)
}
.mark::before {
width: 100%;
height: 1px;
top: 50%
}
.mark::after {
height: 100%;
width: 1px;
left: 50%
}
@keyframes cornerIn {
from {
opacity: 0;
scale: .3
}
}
.m1 {
top: 22px;
left: 22px
}
.m2 {
top: 22px;
right: 22px
}
.m3 {
bottom: 22px;
left: 22px
}
.m4 {
bottom: 22px;
right: 22px
}
.hud-tl,
.hud-tr {
animation: fadeUp .8s .1s backwards
}
.hud-bl,
.hud-br {
animation: fadeUp .8s .25s backwards
}
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(12px)
}
}
.orb {
animation: orbIn 1.1s .15s cubic-bezier(.2, .8, .25, 1) backwards
}
@keyframes orbIn {
from {
opacity: 0;
transform: scale(.82) rotate(-8deg)
}
}
@media(max-width:640px) {
.hud-tr,
.hud-bl {
display: none
}
.hud-br {
bottom: 20px;
right: 20px
}
.hud-tl {
top: 20px;
left: 20px
}
}
@media(prefers-reduced-motion:reduce) {
* {
animation-duration: .01s !important;
animation-iteration-count: 1 !important;
transition: none !important
}
}
</style>
</head>
<body>
<div class="bg-rings" id="bgRings"></div>
<div class="motes" id="motes"></div>
<div class="grain"></div>
<span class="mark m1"></span><span class="mark m2"></span>
<span class="mark m3"></span><span class="mark m4"></span>
<header class="hud hud-tl">
<span class="tag">Specimen №001</span>
<h1 class="display">ORB-<em>01</em></h1>
</header>
<aside class="hud hud-tr">
<p class="readout">
θ <b id="theta">000.0°</b><br>
<span id="rpm">1.7</span> RPM <span class="dot">●</span> LIVE<br>
CURSOR <b id="dist">——</b>
</p>
</aside>
<div class="stage">
<div class="orb" id="orb">
<svg viewBox="0 0 800 800" aria-hidden="true">
<defs>
<radialGradient id="gCore" cx="38%" cy="32%">
<stop offset="0%" stop-color="#fff6e0" />
<stop offset="45%" stop-color="#f5b942" />
<stop offset="100%" stop-color="#ff6b5e" />
</radialGradient>
<filter id="blurGlow" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="26" />
</filter>
</defs>
<!-- crosshair guides -->
<g stroke="#33505a" stroke-width="1">
<line x1="400" y1="30" x2="400" y2="770" />
<line x1="30" y1="400" x2="770" y2="400" />
</g>
<!-- outer dashed ring -->
<circle class="spin r1" cx="400" cy="400" r="362" fill="none" stroke="#54808a" stroke-width="2"
stroke-dasharray="2 15" />
<!-- dial ticks -->
<g id="ticks"></g>
<!-- sweeping arcs -->
<g fill="none" stroke-linecap="round">
<circle class="spin r2" cx="400" cy="400" r="308" stroke="#f5b942" stroke-width="10"
stroke-dasharray="220 1720" />
<circle class="spin r3" cx="400" cy="400" r="308" stroke="#ff6b5e" stroke-width="10"
stroke-dasharray="90 1850" />
<circle class="spin r5" cx="400" cy="400" r="252" stroke="#8fd8e8" stroke-width="3"
stroke-dasharray="140 1448" />
</g>
<circle cx="400" cy="400" r="226" fill="none" stroke="#3f6570" stroke-width="1.5" />
<circle class="spin r4" cx="400" cy="400" r="198" fill="none" stroke="#54808a" stroke-width="1.5"
stroke-dasharray="10 14" />
<!-- satellites -->
<g class="spin s1">
<circle cx="400" cy="92" r="9" fill="#ff6b5e" />
<circle cx="400" cy="92" r="18" fill="none" stroke="#ff6b5e" stroke-opacity=".4" />
</g>
<g class="spin s2">
<circle cx="400" cy="172" r="6" fill="#8fd8e8" />
</g>
<g class="spin s3">
<circle cx="400" cy="148" r="4" fill="#f5b942" />
</g>
<!-- core -->
<circle cx="400" cy="400" r="70" fill="#f5b942" opacity=".35" filter="url(#blurGlow)" id="glow" />
<circle id="core" cx="400" cy="400" r="78" fill="url(#gCore)" />
<circle cx="400" cy="400" r="112" fill="none" stroke="#f5b942" stroke-opacity=".5" stroke-width="1" />
</svg>
</div>
</div>
<footer class="hud hud-bl">
<p class="bl-line">A <b>fancy circle</b>, drawn in layers.</p>
<p class="hint"><kbd>hover</kbd> excite · <kbd>click</kbd> pulse · <kbd>space</kbd> pause</p>
</footer>
<nav class="hud hud-br">
<div class="controls">
<button id="btnHue">HUE ↻</button>
<button id="btnPause">PAUSE</button>
</div>
<label class="speed">SPEED
<input type="range" id="speed" min="0" max="300" value="100">
<b id="spdVal">1.0×</b>
</label>
</footer>
<script>
const $ = s => document.querySelector(s);
const orb = $('#orb'), core = $('#core'), glow = $('#glow');
/* dial ticks */
const ticks = $('#ticks'), NS = 'http://www.w3.org/2000/svg';
for (let i = 0; i < 90; i++) {
const a = i / 90 * Math.PI * 2, long = i % 15 === 0;
const r1 = 338, r2 = long ? 322 : 330;
const l = document.createElementNS(NS, 'line');
l.setAttribute('x1', 400 + Math.cos(a) * r1); l.setAttribute('y1', 400 + Math.sin(a) * r1);
l.setAttribute('x2', 400 + Math.cos(a) * r2); l.setAttribute('y2', 400 + Math.sin(a) * r2);
l.setAttribute('stroke', long ? '#8fd8e8' : '#4a6f79');
l.setAttribute('stroke-width', long ? 2.5 : 1);
ticks.appendChild(l);
}
/* ambient rings + motes */
const bgR = $('#bgRings');
[15, 26, 38, 52].forEach(p => {
const s = document.createElement('span');
s.style.width = s.style.height = p + 'vmin';
bgR.appendChild(s);
});
const motes = $('#motes');
['#f5b942', '#8fd8e8', '#ff6b5e'].forEach(c => {
for (let i = 0; i < 4; i++) {
const m = document.createElement('span');
const sz = 2 + Math.random() * 3;
m.style.cssText = `width:${sz}px;height:${sz}px;background:${c};left:${Math.random() * 100}vw;top:${100 - Math.random() * 40}vh;animation-duration:${14 + Math.random() * 16}s;animation-delay:${-Math.random() * 20}s;opacity:.5`;
motes.appendChild(m);
}
});
/* mouse: parallax tilt + cursor distance */
let mx = innerWidth / 2, my = innerHeight / 2, tx = 0, ty = 0;
addEventListener('pointermove', e => {
mx = e.clientX; my = e.clientY;
tx = (e.clientX / innerWidth - .5) * 16;
ty = -(e.clientY / innerHeight - .5) * 16;
});
let cx = 0, cy = 0;
function loop() {
cx += (tx - cx) * .06; cy += (ty - cy) * .06;
orb.style.transform = `rotateY(${cx}deg) rotateX(${cy}deg)`;
requestAnimationFrame(loop);
}
loop();
/* live telemetry */
let theta = 0;
setInterval(() => {
theta = (theta + 0.25 * (+$('#speed').value / 100)) % 360;
$('#theta').textContent = theta.toFixed(1).padStart(5, '0') + '°';
}, 80);
setInterval(() => {
const r = orb.getBoundingClientRect();
const d = Math.hypot(mx - (r.left + r.width / 2), my - (r.top + r.height / 2));
$('#dist').textContent = Math.round(d) + 'px';
$('#rpm').textContent = (360 / 70 * (+$('#speed').value / 100)).toFixed(1);
}, 120);
/* click: ripple + core flash */
orb.addEventListener('click', e => {
const rip = document.createElement('span');
rip.className = 'ripple';
const size = orb.getBoundingClientRect().width * 0.4;
rip.style.width = rip.style.height = size + 'px';
rip.style.borderLeftColor = rip.style.borderTopColor = '#f5b942';
rip.style.animation = 'rip .9s ease-out forwards';
orb.appendChild(rip);
setTimeout(() => rip.remove(), 900);
core.classList.remove('flash'); void core.offsetWidth; core.classList.add('flash');
});
/* controls */
$('#speed').addEventListener('input', e => {
$('#spdVal').textContent = (e.target.value / 100).toFixed(1) + '×';
orb.style.animationPlayState;
document.querySelectorAll('.orb svg g, .orb svg circle').forEach(el => { });
orb.getAnimations?.();
});
function setSpeed(v) {
orb.style.setProperty('--sp', '');
orb.querySelectorAll('*').forEach(el => { });
const dur = el => el.style.animationDuration;
/* apply via animationRate if supported, fallback: restart durations */
if (orb.getAnimations) {
orb.getAnimations({ subtree: true }).forEach(a => {
if (a.animationName && a.animationName !== 'breathe') a.playbackRate = v;
});
}
}
$('#speed').addEventListener('input', e => setSpeed(e.target.value / 100));
$('#btnPause').addEventListener('click', e => {
orb.classList.toggle('paused');
e.target.textContent = orb.classList.contains('paused') ? 'RESUME' : 'PAUSE';
});
addEventListener('keydown', e => {
if (e.code === 'Space') { e.preventDefault(); $('#btnPause').click(); }
});
$('#btnHue').addEventListener('click', () => {
document.documentElement.style.filter = `hue-rotate(${(Math.random() * 360) | 0}deg)`;
});
</script>
</body>
</html>
<script type="text/plain" id="launch-params">
llama-cli --prompt create a fancy circle in html --seed 1337 --single-turn --output output.txt --flash-attn on --load-mode dio --fit on --parallel 1 --threads 32 --fit-target 0 --n-gpu-layers all --cpu-strict 1 --log-verbosity 3 --batch-size 2048 --ubatch-size 512 --spec-type draft-mtp --spec-draft-n-max 5 --cache-type-k-draft q4_0 --cache-type-v-draft q4_0 --model /run/media/caps/extra/llm-models/Qwen3.8-27B-UD-Q4_K_XL.gguf --lora-scaled /run/media/caps/extra/llm-models/qwen36-27b-thinkingcap-lora-rank64.gguf:40 --cache-type-k q8_0 --cache-type-v q8_0 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --temperature 1.0 --top-p 0.95 --presence-penalty 0.0 --reasoning on --reasoning-effort xhigh
</script>
<script type="text/plain" id="chat">
User:
create a fancy circle in html
Assistant:
[Start thinking]
The user wants a "fancy circle" in HTML. I want to build a rich, crafted page: motion, micro-interactions, a layered background, strong typographic contrast. A single HTML file.
"Fancy circle" — this could be an interactive kinetic art piece: concentric rings, orbiting satellites, dashed rings, gradient stroke animation, mouse-interactive, rotating dashed circles, pulsing core, particle orbits. I should make it feel alive.
Design direction: start with the circle itself — the most distinctive thing about the subject. So the circle should dominate the viewport, and I can place a specimen label in the corner (like a lab/observatory aesthetic). Pair a distinctive display face with a legible body face.
Palette: a deep petrol/teal background with multiple warm accents (amber, coral, mint), or a deep blue-green with warm gold + soft rose + off-white. Or a warm papery light theme? Circles look great on a deep background with glowing rings. Let's go with deep teal/petrol (around #0d1b21) with layered radial glows, and multiple accents: gold (#f5b942), coral (#ff6b5e), ice (#9adcf0). That's multi-accent.
Alternative: light theme — warm ivory? A cool light gray-blue paper with bold ink circles, Swiss/observatory chart style. Hmm. "Fancy" suggests showy, glowing, kinetic. Deep petrol with glow feels right.
Typography: display face — something distinctive: "Unbounded" or "Clash Display" (not on Google), "Space Grotesk" is common… Google Fonts options: "Unbounded" (very distinctive, round geometric — perfect for circles), body: "Instrument Sans" or "Sora"? Unbounded for display, "Spline Sans Mono" for labels/data, and body could just be monospace or a humanist sans. Pair: Unbounded (display) + Space Mono? Space Mono is quirky. Or "Unbounded" + "Manrope". Let's go with Unbounded + Space Mono for instrument readouts, and Manrope as a body fallback. Actually 2 faces is enough: Unbounded for display, Space Mono for body/labels — gives an observatory instrument feel. Body text is minimal anyway.
Features:
- Full-viewport kinetic circle: layered rings:
- Outermost: dashed ring slowly rotating
- Ring of tick marks (like a dial)
- Arc segments (SVG stroke-dasharray) rotating at different speeds
- Orbiting dots/satellites on different orbits
- Pulsing core with radial glow
- Grain/noise overlay
- Mouse interaction: parallax tilt of the whole assembly (3D perspective), rings counter-rotate toward the cursor, core glows brighter near the cursor.
- Click: pulse ripple emission.
- Controls: a small panel with buttons to change modes? Keep scope reasonable but rich: "speed" slider, palette shift button, pause. Could also do scroll-driven: as you scroll the circle transforms? The prompt is minimal — single view is fine but adding a bit of scroll depth is nice. A single full-screen piece with a corner HUD is probably best; scroll not necessary. But adding a little: footer strip with spec readouts.
Micro-interactions:
- Hover on circle: ring speeds up slightly, core breathes faster
- Click: ripple ring expands
- Keyboard: space to pause
- Cursor proximity: satellites lean away/toward
HUD elements:
- Top-left: small wordmark "ORB-01 / KINETIC SPECIMEN"
- Top-right: live readouts (rotation degrees, RPM, cursor distance)
- Bottom-left: caption + hint
- Bottom-right: controls (speed, hue, pause)
- Corner crosshair marks for the instrument feel
Background: layered — radial vignette, faint concentric guide rings across the whole page, drifting grain, subtle starfield? Faint dots drifting could work.
Implementation: one HTML file, inline CSS + JS. Use SVG for crisp rings, CSS animations for rotation, JS for parallax + ripple + readouts + controls.
Plan for the SVG circle assembly (viewBox 0 0 800 800, center 400,400):
1. defs: radial gradient for core, glow filter
2. Outer dashed ring r=360, stroke dasharray small, class slow rotation
3. Ticks: generate 72 ticks in JS, or use stroke-dasharray on a circle: r=330, dasharray computed so ticks. Easier: JS-generated lines.
4. Arc ring: r=300, 2–3 arcs (stroke-dasharray with big gaps), rounded linecap, different accent colors, rotating
5. Thin solid ring r=260
6. Inner dashed counter-rotating r=230
7. Satellite orbit: dots positioned by JS animation along circular paths (r=300, 230, 170)
8. Core: r=90 circle with radial gradient, pulse scale; plus ring r=120
9. Crosshair lines through the center, subtle
[End thinking]
Here's a kinetic, instrument-style orb — layered SVG rings, orbiting satellites, live telemetry, and mouse/click interaction, all in one file:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ORB-01 · Kinetic Circle</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@300;500;800&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
:root{
--bg0:#07161b; --bg1:#0d2229;
--ink:#e8f4f4; --dim:#7fa3a8; --faint:#33505a;
--gold:#f5b942; --coral:#ff6b5e; --ice:#8fd8e8;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{height:100%}
body{
background:radial-gradient(120% 90% at 50% 38%, var(--bg1) 0%, var(--bg0) 62%, #04090c 100%);
color:var(--ink);
font-family:'Space Mono', monospace;
overflow:hidden;
user-select:none;
}
::selection{background:var(--gold);color:#04141a}
/* ---- ambient layers ---- */
.bg-rings{position:fixed;inset:-40%;pointer-events:none;opacity:.5}
.bg-rings span{
position:absolute;top:50%;left:50%;translate:-50% -50%;
border:1px dashed var(--faint);border-radius:50%;
}
.grain{
position:fixed;inset:0;pointer-events:none;opacity:.07;mix-blend-mode:overlay;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.motes span{
position:fixed;border-radius:50%;pointer-events:none;
animation:drift linear infinite;
}
@keyframes drift{
0%{transform:translateY(12vh) scale(.6);opacity:0}
12%{opacity:.9}
88%{opacity:.9}
100%{transform:translateY(-115vh) scale(1.1);opacity:0}
}
/* ---- stage ---- */
.stage{position:fixed;inset:0;display:grid;place-items:center;perspective:1100px}
.orb{
position:relative;width:min(74vmin,620px);aspect-ratio:1;
transform-style:preserve-3d;will-change:transform;
cursor:crosshair;
}
.orb svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible;display:block}
.spin{transform-box:fill-box;transform-origin:center}
@keyframes rot{to{transform:rotate(360deg)}}
@keyframes rotR{to{transform:rotate(-360deg)}}
.r1{animation:rot 70s linear infinite}
.r2{animation:rotR 26s linear infinite}
.r3{animation:rot 44s linear infinite}
.r4{animation:rotR 18s linear infinite}
.r5{animation:rot 32s linear infinite}
.s1{animation:rot 12s linear infinite}
.s2{animation:rotR 20s linear infinite}
.s3{animation:rot 30s linear infinite}
.orb.paused *{animation-play-state:paused}
.orb svg *{transition:stroke .5s, fill .5s}
.orb::before{
content:"";position:absolute;inset:-30%;border-radius:50%;
background:radial-gradient(circle, rgba(143,216,232,.14), transparent 62%);
opacity:0;transition:opacity .6s;pointer-events:none;
}
.orb:hover::before{opacity:1}
.orb:hover .r2{animation-duration:9s}
.orb:hover .r4{animation-duration:6s}
.orb:hover .s1{animation-duration:5s}
.orb:hover #core{animation-duration:1.5s}
#core{transform-box:fill-box;transform-origin:center;animation:breathe 3.4s ease-in-out infinite}
@keyframes breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
.ripple{
position:absolute;top:50%;left:50%;translate:-50% -50%;
border:2px solid var(--ice);border-radius:50%;pointer-events:none;
}
@keyframes rip{to{width:160%;height:160%;opacity:0;border-width:1px}}
@keyframes flash{to{transform:scale(1.35);opacity:.9}}
#core.flash{animation:flash .45s ease-out}
/* ---- HUD ---- */
.hud{position:fixed;pointer-events:none;z-index:5}
.hud-tl{top:28px;left:32px}
.hud-tr{top:28px;right:32px;text-align:right}
.hud-bl{bottom:26px;left:32px}
.hud-br{bottom:26px;right:32px;text-align:right}
.tag{
display:inline-flex;align-items:center;gap:9px;
font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:var(--dim);
}
.tag::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--gold);
animation:blink 2.2s ease-in-out infinite}
@keyframes blink{50%{opacity:.25}}
.display{
font-family:'Unbounded',sans-serif;font-weight:800;
font-size:clamp(1.15rem,2.6vw,1.9rem);letter-spacing:.14em;margin-top:10px;
}
.display em{font-style:normal;font-weight:300;color:var(--gold)}
.readout{font-size:10.5px;line-height:1.9;color:var(--dim);letter-spacing:.08em}
.readout b{color:var(--ink);font-weight:400}
.readout .dot{color:var(--coral)}
.bl-line{
font-family:'Unbounded',sans-serif;font-weight:300;
font-size:clamp(.8rem,1.5vw,1rem);letter-spacing:.04em;margin-bottom:10px;
}
.bl-line b{font-weight:500;color:var(--ice)}
.hint{font-size:10px;letter-spacing:.14em;color:var(--faint)}
.hint kbd{
border:1px solid var(--faint);border-radius:3px;padding:1px 5px;color:var(--dim);
}
.controls{pointer-events:auto;display:flex;gap:10px;justify-content:flex-end;margin-bottom:14px}
.controls button{
font-family:'Space Mono',monospace;font-size:10px;letter-spacing:.2em;
background:transparent;border:1px solid var(--faint);color:var(--dim);
padding:8px 14px;border-radius:999px;cursor:pointer;
transition:all .25s;
}
.controls button:hover{border-color:var(--gold);color:var(--gold);transform:translateY(-2px)}
.controls button:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.speed{pointer-events:auto;display:flex;align-items:center;gap:10px;font-size:9px;
letter-spacing:.3em;color:var(--faint)}
.speed input{
-webkit-appearance:none;appearance:none;width:120px;height:2px;
background:var(--faint);outline:none;cursor:pointer;
}
.speed input::-webkit-slider-thumb{
-webkit-appearance:none;width:12px;height:12px;border-radius:50%;
background:var(--gold);border:2px solid var(--bg0);
transition:transform .2s;
}
.speed input::-webkit-slider-thumb:hover{transform:scale(1.35)}
.speed input::-moz-range-thumb{
width:12px;height:12px;border-radius:50%;background:var(--gold);
border:2px solid var(--bg0);
}
.speed b{color:var(--gold);font-weight:400;min-width:4ch}
.mark{position:fixed;width:18px;height:18px;pointer-events:none;z-index:4;
opacity:.45;animation:cornerIn 1s .3s backwards}
.mark::before,.mark::after{content:"";position:absolute;background:var(--faint)}
.mark::before{width:100%;height:1px;top:50%}
.mark::after{height:100%;width:1px;left:50%}
@keyframes cornerIn{from{opacity:0;scale:.3}}
.m1{top:22px;left:22px}.m2{top:22px;right:22px}
.m3{bottom:22px;left:22px}.m4{bottom:22px;right:22px}
.hud-tl,.hud-tr{animation:fadeUp .8s .1s backwards}
.hud-bl,.hud-br{animation:fadeUp .8s .25s backwards}
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}}
.orb{animation:orbIn 1.1s .15s cubic-bezier(.2,.8,.25,1) backwards}
@keyframes orbIn{from{opacity:0;transform:scale(.82) rotate(-8deg)}}
@media(max-width:640px){
.hud-tr,.hud-bl{display:none}
.hud-br{bottom:20px;right:20px}
.hud-tl{top:20px;left:20px}
}
@media(prefers-reduced-motion:reduce){
*{animation-duration:.01s!important;animation-iteration-count:1!important;transition:none!important}
}
</style>
</head>
<body>
<div class="bg-rings" id="bgRings"></div>
<div class="motes" id="motes"></div>
<div class="grain"></div>
<span class="mark m1"></span><span class="mark m2"></span>
<span class="mark m3"></span><span class="mark m4"></span>
<header class="hud hud-tl">
<span class="tag">Specimen №001</span>
<h1 class="display">ORB-<em>01</em></h1>
</header>
<aside class="hud hud-tr">
<p class="readout">
θ <b id="theta">000.0°</b><br>
<span id="rpm">1.7</span> RPM <span class="dot">●</span> LIVE<br>
CURSOR <b id="dist">——</b>
</p>
</aside>
<div class="stage">
<div class="orb" id="orb">
<svg viewBox="0 0 800 800" aria-hidden="true">
<defs>
<radialGradient id="gCore" cx="38%" cy="32%">
<stop offset="0%" stop-color="#fff6e0"/>
<stop offset="45%" stop-color="#f5b942"/>
<stop offset="100%" stop-color="#ff6b5e"/>
</radialGradient>
<filter id="blurGlow" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="26"/>
</filter>
</defs>
<!-- crosshair guides -->
<g stroke="#33505a" stroke-width="1">
<line x1="400" y1="30" x2="400" y2="770"/>
<line x1="30" y1="400" x2="770" y2="400"/>
</g>
<!-- outer dashed ring -->
<circle class="spin r1" cx="400" cy="400" r="362" fill="none"
stroke="#54808a" stroke-width="2" stroke-dasharray="2 15"/>
<!-- dial ticks -->
<g id="ticks"></g>
<!-- sweeping arcs -->
<g fill="none" stroke-linecap="round">
<circle class="spin r2" cx="400" cy="400" r="308"
stroke="#f5b942" stroke-width="10" stroke-dasharray="220 1720"/>
<circle class="spin r3" cx="400" cy="400" r="308"
stroke="#ff6b5e" stroke-width="10" stroke-dasharray="90 1850"/>
<circle class="spin r5" cx="400" cy="400" r="252"
stroke="#8fd8e8" stroke-width="3" stroke-dasharray="140 1448"/>
</g>
<circle cx="400" cy="400" r="226" fill="none" stroke="#3f6570" stroke-width="1.5"/>
<circle class="spin r4" cx="400" cy="400" r="198" fill="none"
stroke="#54808a" stroke-width="1.5" stroke-dasharray="10 14"/>
<!-- satellites -->
<g class="spin s1">
<circle cx="400" cy="92" r="9" fill="#ff6b5e"/>
<circle cx="400" cy="92" r="18" fill="none" stroke="#ff6b5e" stroke-opacity=".4"/>
</g>
<g class="spin s2">
<circle cx="400" cy="172" r="6" fill="#8fd8e8"/>
</g>
<g class="spin s3">
<circle cx="400" cy="148" r="4" fill="#f5b942"/>
</g>
<!-- core -->
<circle cx="400" cy="400" r="70" fill="#f5b942" opacity=".35" filter="url(#blurGlow)" id="glow"/>
<circle id="core" cx="400" cy="400" r="78" fill="url(#gCore)"/>
<circle cx="400" cy="400" r="112" fill="none" stroke="#f5b942" stroke-opacity=".5" stroke-width="1"/>
</svg>
</div>
</div>
<footer class="hud hud-bl">
<p class="bl-line">A <b>fancy circle</b>, drawn in layers.</p>
<p class="hint"><kbd>hover</kbd> excite · <kbd>click</kbd> pulse · <kbd>space</kbd> pause</p>
</footer>
<nav class="hud hud-br">
<div class="controls">
<button id="btnHue">HUE ↻</button>
<button id="btnPause">PAUSE</button>
</div>
<label class="speed">SPEED
<input type="range" id="speed" min="0" max="300" value="100">
<b id="spdVal">1.0×</b>
</label>
</footer>
<SCRIPT_ESCAPE>
const $=s=>document.querySelector(s);
const orb=$('#orb'), core=$('#core'), glow=$('#glow');
/* dial ticks */
const ticks=$('#ticks'), NS='http://www.w3.org/2000/svg';
for(let i=0;i<90;i++){
const a=i/90*Math.PI*2, long=i%15===0;
const r1=338, r2=long?322:330;
const l=document.createElementNS(NS,'line');
l.setAttribute('x1',400+Math.cos(a)*r1); l.setAttribute('y1',400+Math.sin(a)*r1);
l.setAttribute('x2',400+Math.cos(a)*r2); l.setAttribute('y2',400+Math.sin(a)*r2);
l.setAttribute('stroke', long?'#8fd8e8':'#4a6f79');
l.setAttribute('stroke-width', long?2.5:1);
ticks.appendChild(l);
}
/* ambient rings + motes */
const bgR=$('#bgRings');
[15,26,38,52].forEach(p=>{
const s=document.createElement('span');
s.style.width=s.style.height=p+'vmin';
bgR.appendChild(s);
});
const motes=$('#motes');
['#f5b942','#8fd8e8','#ff6b5e'].forEach(c=>{
for(let i=0;i<4;i++){
const m=document.createElement('span');
const sz=2+Math.random()*3;
m.style.cssText=`width:${sz}px;height:${sz}px;background:${c};left:${Math.random()*100}vw;top:${100-Math.random()*40}vh;animation-duration:${14+Math.random()*16}s;animation-delay:${-Math.random()*20}s;opacity:.5`;
motes.appendChild(m);
}
});
/* mouse: parallax tilt + cursor distance */
let mx=innerWidth/2, my=innerHeight/2, tx=0, ty=0;
addEventListener('pointermove',e=>{
mx=e.clientX; my=e.clientY;
tx=(e.clientX/innerWidth-.5)*16;
ty=-(e.clientY/innerHeight-.5)*16;
});
let cx=0, cy=0;
function loop(){
cx+=(tx-cx)*.06; cy+=(ty-cy)*.06;
orb.style.transform=`rotateY(${cx}deg) rotateX(${cy}deg)`;
requestAnimationFrame(loop);
}
loop();
/* live telemetry */
let theta=0;
setInterval(()=>{
theta=(theta+0.25*(+$('#speed').value/100))%360;
$('#theta').textContent=theta.toFixed(1).padStart(5,'0')+'°';
},80);
setInterval(()=>{
const r=orb.getBoundingClientRect();
const d=Math.hypot(mx-(r.left+r.width/2),my-(r.top+r.height/2));
$('#dist').textContent=Math.round(d)+'px';
$('#rpm').textContent=(360/70*(+$('#speed').value/100)).toFixed(1);
},120);
/* click: ripple + core flash */
orb.addEventListener('click',e=>{
const rip=document.createElement('span');
rip.className='ripple';
const size=orb.getBoundingClientRect().width*0.4;
rip.style.width=rip.style.height=size+'px';
rip.style.borderLeftColor=rip.style.borderTopColor='#f5b942';
rip.style.animation='rip .9s ease-out forwards';
orb.appendChild(rip);
setTimeout(()=>rip.remove(),900);
core.classList.remove('flash'); void core.offsetWidth; core.classList.add('flash');
});
/* controls */
$('#speed').addEventListener('input',e=>{
$('#spdVal').textContent=(e.target.value/100).toFixed(1)+'×';
orb.style.animationPlayState;
document.querySelectorAll('.orb svg g, .orb svg circle').forEach(el=>{});
orb.getAnimations?.();
});
function setSpeed(v){
orb.style.setProperty('--sp','');
orb.querySelectorAll('*').forEach(el=>{});
const dur=el=>el.style.animationDuration;
/* apply via animationRate if supported, fallback: restart durations */
if(orb.getAnimations){
orb.getAnimations({subtree:true}).forEach(a=>{
if(a.animationName&&a.animationName!=='breathe')a.playbackRate=v;
});
}
}
$('#speed').addEventListener('input',e=>setSpeed(e.target.value/100));
$('#btnPause').addEventListener('click',e=>{
orb.classList.toggle('paused');
e.target.textContent=orb.classList.contains('paused')?'RESUME':'PAUSE';
});
addEventListener('keydown',e=>{
if(e.code==='Space'){e.preventDefault();$('#btnPause').click();}
});
$('#btnHue').addEventListener('click',()=>{
document.documentElement.style.filter=`hue-rotate(${(Math.random()*360)|0}deg)`;
});
</SCRIPT_ESCAPE>
</body>
</html>
```
**What's inside:**
- **9-layer circle** — dashed halo, 90-tick dial, sweeping gold/coral/ice arcs, counter-rotating rings, three orbiting satellites, and a pulsing gradient core
- **Live telemetry** — rotation angle, RPM, and cursor distance update in real time
- **Interactions** — 3D tilt follows your mouse, hovering accelerates the inner rings, clicking fires a ripple + core flash, `space` pauses
- **Controls** — speed slider (0–3×), pause/resume, and a `HUE ↻` button that re-colors the whole piece
- Ambient floating motes, faint page-scale guide rings, film grain, and staggered load-in
Save as `orb.html` and open in any browser. Want it remade as a flat light-mode version, or wired to scroll instead of hover?
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment