Runs/circle · chain · Haiku 4.5/017
circle · chain · Haiku 4.5 · step 017 of 64
offline render, Chrome 154, light scheme, 2 s of virtual time
This page calls Math.random or the clock, so a live run can differ from this render.
runs in a sandboxed frame with no origin; live pages may fetch CDN fonts the offline render did not
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Circle Display</title>
<style>
:root {
--color-white: #ffffff;
--color-gold: #ffd700;
--color-pink: #ff6b9d;
--color-teal: #4ecdc4;
--color-mint: #95e1d3;
--shadow-color: rgba(0, 0, 0, 0.3);
--transition-normal: 0.3s ease;
--transition-slow: 0.6s ease-out;
--color-mode-1-rgb: 255, 107, 157;
--color-mode-2-rgb: 78, 205, 196;
--color-mode-3-rgb: 149, 225, 211;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
background: linear-gradient(135deg, #5f6fd1 0%, #6b3fa0 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: var(--color-white);
font-size: 14px;
padding: 20px;
min-height: 100vh;
}
.circle-container {
cursor: pointer;
border: none;
background: none;
padding: 0;
position: relative;
will-change: transform;
}
.circle-container:hover {
transform: scale(1.06);
}
.circle-container:focus {
outline: none;
}
.circle-container:focus-visible {
transform: scale(1.08);
outline: 3px solid rgba(255, 255, 255, 0.7);
outline-offset: 12px;
border-radius: 50%;
}
svg {
width: min(400px, 80vw);
height: min(400px, 80vw);
display: block;
animation: fadeIn var(--transition-slow);
}
circle {
fill: var(--color-white);
stroke: rgba(255, 255, 255, 0.2);
stroke-width: 2;
animation: pulse 3s ease-in-out infinite;
filter: drop-shadow(0 20px 40px var(--shadow-color));
transition: fill 0.3s ease, r 0.3s ease, stroke 0.3s ease, filter 0.3s ease;
will-change: r, fill;
}
.circle-container:is(:hover, :focus-visible) circle {
fill: #e8f4f8;
r: 205;
stroke: rgba(255, 255, 255, 0.4);
}
.circle-container.active circle {
fill: var(--color-gold);
stroke: rgba(255, 215, 0, 0.3);
animation: pulse-active 0.6s ease-in-out;
filter: drop-shadow(0 20px 40px rgba(255, 215, 0, 0.5));
r: 200;
}
.circle-container.active:hover circle {
fill: #ffed4e;
}
[class*="color-mode-"] circle {
fill: var(--mode-color);
stroke: rgba(var(--mode-rgb), 0.3);
filter: drop-shadow(0 20px 40px rgba(var(--mode-rgb), 0.3));
}
[class*="color-mode-"]:is(:hover, :focus-visible) circle {
stroke: rgba(var(--mode-rgb), 0.5);
}
[class*="color-mode-"].active circle {
filter: drop-shadow(0 20px 40px rgba(var(--mode-rgb), 0.5));
}
.circle-container.color-mode-1 { --mode-color: var(--color-pink); --mode-rgb: var(--color-mode-1-rgb); }
.circle-container.color-mode-2 { --mode-color: var(--color-teal); --mode-rgb: var(--color-mode-2-rgb); }
.circle-container.color-mode-3 { --mode-color: var(--color-mint); --mode-rgb: var(--color-mode-3-rgb); }
.hint {
font-size: 12px;
opacity: 0.8;
transition: opacity var(--transition-normal);
letter-spacing: 0.5px;
max-width: 85%;
line-height: 1.6;
margin-top: 12px;
position: relative;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
will-change: opacity;
}
.circle-container:hover ~ .hint,
.circle-container:focus-visible ~ .hint {
opacity: 1;
}
.mode-indicator {
font-size: 10px;
opacity: 0;
margin-top: 8px;
letter-spacing: 1px;
text-transform: uppercase;
transition: opacity var(--transition-normal);
height: 16px;
}
.mode-indicator.visible {
opacity: 0.9;
}
.controls {
display: flex;
gap: 12px;
margin-top: 10px;
flex-wrap: wrap;
justify-content: center;
width: 100%;
}
.control-btn {
padding: 8px 16px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
background: rgba(255, 255, 255, 0.15);
color: var(--color-white);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
cursor: pointer;
transition: background-color var(--transition-normal), border-color var(--transition-normal), transform var(--transition-normal), box-shadow var(--transition-normal);
backdrop-filter: blur(4px);
will-change: transform;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.control-btn:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.control-btn:focus {
outline: none;
}
.control-btn:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.7);
outline-offset: 4px;
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@keyframes pulse {
0%, 100% { r: 200; opacity: 0.95; }
50% { r: 203; opacity: 1; }
}
@keyframes pulse-active {
0% { r: 200; }
50% { r: 210; }
100% { r: 200; }
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
@keyframes colorShift {
0% { filter: saturate(1) brightness(1); }
50% { filter: saturate(1.3) brightness(1.1); }
100% { filter: saturate(1) brightness(1); }
}
.color-shifting circle {
animation: colorShift 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
transform-origin: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
text-align: center;
width: 100%;
}
@media (max-width: 480px) {
body {
gap: 12px;
padding: 16px;
}
svg {
width: min(320px, 75vw);
height: min(320px, 75vw);
}
.hint {
font-size: 11px;
max-width: 90%;
margin-top: 10px;
}
.mode-indicator {
font-size: 9px;
}
.control-btn {
padding: 10px 18px;
font-size: 10px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
}
@media (max-width: 360px) {
body {
gap: 8px;
padding: 12px;
}
svg {
width: min(280px, 70vw);
height: min(280px, 70vw);
}
.controls {
flex-wrap: wrap;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
@supports (backdrop-filter: blur(0)) {
.control-btn {
background: rgba(255, 255, 255, 0.1);
}
}
@supports not (backdrop-filter: blur(0)) {
.control-btn {
background: rgba(0, 0, 0, 0.3);
}
}
</style>
</head>
<body>
<main>
<button class="circle-container" id="decorative-circle" aria-label="Interactive circle - click to toggle" aria-pressed="false">
<svg viewBox="0 0 400 400" aria-hidden="true" role="presentation">
<circle cx="200" cy="200" r="200"/>
</svg>
</button>
<div class="mode-indicator" id="mode-indicator" aria-live="polite" aria-atomic="true" aria-label="Current color mode"></div>
<div class="controls" role="group" aria-label="Circle controls">
<button class="control-btn" id="cycle-btn" aria-label="Cycle to next color">Cycle Color</button>
<button class="control-btn" id="reset-btn" aria-label="Reset to default state">Reset</button>
</div>
</main>
<div class="hint" id="hint-text" role="status" aria-live="polite" aria-relevant="text">Click to activate • Double-click to change color</div>
<script>
const CONFIG = {
storageKey: 'circle-state',
tapDelay: 300,
colorShiftDuration: 500,
colorModes: [
{ class: 'default', name: 'Default White' },
{ class: 'color-mode-1', name: 'Pink' },
{ class: 'color-mode-2', name: 'Teal' },
{ class: 'color-mode-3', name: 'Mint' }
],
isTouch: 'ontouchstart' in window || navigator.maxTouchPoints > 0
};
const el = {
circle: document.getElementById('decorative-circle'),
hint: document.getElementById('hint-text'),
modeIndicator: document.getElementById('mode-indicator'),
cycleBtn: document.getElementById('cycle-btn'),
resetBtn: document.getElementById('reset-btn')
};
let state = { active: false, colorMode: 0 };
let lastTapTime = 0;
const getColorModeClass = (mode) => CONFIG.colorModes[mode].class;
const getColorModeName = (mode) => CONFIG.colorModes[mode].name;
const storage = {
save() {
try {
localStorage.setItem(CONFIG.storageKey, JSON.stringify(state));
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.warn('localStorage quota exceeded');
} else {
console.warn('Failed to save state:', e);
}
}
},
load() {
try {
const saved = localStorage.getItem(CONFIG.storageKey);
if (saved) {
const parsed = JSON.parse(saved);
if (parsed && typeof parsed === 'object') {
state = { ...state, ...parsed };
}
}
} catch (e) {
console.warn('Failed to load state:', e);
}
}
};
const updateClasses = () => {
const colorClasses = CONFIG.colorModes.map(m => m.class);
el.circle.classList.remove('active', ...colorClasses);
el.circle.setAttribute('aria-pressed', state.active);
if (state.active) el.circle.classList.add('active');
if (state.colorMode > 0) el.circle.classList.add(getColorModeClass(state.colorMode));
};
const updateUI = () => {
const mainText = state.active ? 'Active!' : 'Click to activate';
let hintParts = [mainText];
if (CONFIG.isTouch) {
hintParts.push(state.active ? 'Tap to change color' : 'Tap twice to change color');
} else {
hintParts.push('Double-click to change color • Press C or R for shortcuts');
}
el.hint.textContent = hintParts.join(' • ');
const showMode = state.active && state.colorMode > 0;
el.modeIndicator.classList.toggle('visible', showMode);
if (showMode) {
const modeName = getColorModeName(state.colorMode);
el.modeIndicator.textContent = modeName;
}
};
const applyState = () => {
updateClasses();
updateUI();
};
const toggleActive = () => {
state.active = !state.active;
applyState();
storage.save();
};
const cycleColor = () => {
state.colorMode = (state.colorMode + 1) % CONFIG.colorModes.length;
state.active = true;
el.circle.classList.add('color-shifting');
setTimeout(() => el.circle.classList.remove('color-shifting'), CONFIG.colorShiftDuration);
applyState();
storage.save();
};
const resetState = () => {
state = { active: false, colorMode: 0 };
applyState();
storage.save();
};
const handleDblClick = (e) => {
e.preventDefault();
cycleColor();
};
const handleTouchEnd = (e) => {
const now = Date.now();
if (now - lastTapTime < CONFIG.tapDelay && lastTapTime > 0) {
cycleColor();
lastTapTime = 0;
} else {
lastTapTime = now;
}
};
const keyHandlers = {
' ': toggleActive,
'enter': toggleActive,
'c': cycleColor,
'r': resetState
};
const handleKeyDown = (e) => {
const handler = keyHandlers[e.key.toLowerCase()];
if (handler) {
e.preventDefault();
handler();
}
};
el.circle.addEventListener('click', toggleActive);
el.circle.addEventListener('dblclick', handleDblClick);
el.circle.addEventListener('touchend', handleTouchEnd);
el.circle.addEventListener('blur', () => { lastTapTime = 0; });
document.addEventListener('keydown', handleKeyDown);
el.cycleBtn.addEventListener('click', cycleColor);
el.resetBtn.addEventListener('click', resetState);
const initApp = () => {
storage.load();
applyState();
requestAnimationFrame(() => el.circle.focus());
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initApp);
} else {
initApp();
}
</script>
</body>
</html>
what changed from step 016
+57 / -31 lines
animation: pulse 3s ease-in-out infinite; filter: drop-shadow(0 20px 40px var(--shadow-color));- transition: fill var(--transition-normal), r 0.3s ease, stroke var(--transition-normal), filter var(--transition-normal);- will-change: r, fill, stroke, filter, opacity;+ transition: fill 0.3s ease, r 0.3s ease, stroke 0.3s ease, filter 0.3s ease;+ will-change: r, fill; } .circle-container:is(:hover, :focus-visible) circle {⋯ line 112 .hint { font-size: 12px;- opacity: 0.75;+ opacity: 0.8; transition: opacity var(--transition-normal); letter-spacing: 0.5px;- max-width: 80%;+ max-width: 85%; line-height: 1.6;- margin-top: 8px;+ margin-top: 12px; position: relative; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);+ will-change: opacity; } .circle-container:hover ~ .hint,⋯ line 143 display: flex; gap: 12px;- margin-top: 8px;+ margin-top: 10px;+ flex-wrap: wrap;+ justify-content: center;+ width: 100%; } ⋯ line 160 border-radius: 20px; cursor: pointer;- transition: all var(--transition-normal);+ transition: background-color var(--transition-normal), border-color var(--transition-normal), transform var(--transition-normal), box-shadow var(--transition-normal); backdrop-filter: blur(4px);- will-change: transform, box-shadow, background-color;+ will-change: transform; } ⋯ line 169 border-color: rgba(255, 255, 255, 0.5); transform: translateY(-2px);- box-shadow: 0 12px 20px rgba(0, 0, 0, 0.3);+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } .control-btn:active {- transform: translateY(0) scale(0.96);- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);+ transform: translateY(0) scale(0.98);+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .control-btn:focus {⋯ line 230 font-size: 11px; max-width: 90%;+ margin-top: 10px; } .mode-indicator {⋯ line 236 } .control-btn {- padding: 7px 14px;+ padding: 10px 18px; font-size: 10px;+ min-height: 44px;+ display: flex;+ align-items: center;+ justify-content: center; } }⋯ line 263 } }+ @supports (backdrop-filter: blur(0)) {+ .control-btn {+ background: rgba(255, 255, 255, 0.1);+ }+ }+ @supports not (backdrop-filter: blur(0)) {+ .control-btn {+ background: rgba(0, 0, 0, 0.3);+ }+ } </style> </head>⋯ line 278 <main> <button class="circle-container" id="decorative-circle" aria-label="Interactive circle - click to toggle" aria-pressed="false">- <svg viewBox="0 0 400 400" aria-hidden="true">+ <svg viewBox="0 0 400 400" aria-hidden="true" role="presentation"> <circle cx="200" cy="200" r="200"/> </svg> </button>- <div class="mode-indicator" id="mode-indicator" aria-live="polite" aria-atomic="true"></div>- <div class="controls">+ <div class="mode-indicator" id="mode-indicator" aria-live="polite" aria-atomic="true" aria-label="Current color mode"></div>+ <div class="controls" role="group" aria-label="Circle controls"> <button class="control-btn" id="cycle-btn" aria-label="Cycle to next color">Cycle Color</button> <button class="control-btn" id="reset-btn" aria-label="Reset to default state">Reset</button> </div> </main>- <div class="hint" id="hint-text" role="status" aria-live="polite">Click to activate • Double-click to change color</div>+ <div class="hint" id="hint-text" role="status" aria-live="polite" aria-relevant="text">Click to activate • Double-click to change color</div> <script>⋯ line 358 if (CONFIG.isTouch) {- hintParts.push(state.active ? 'Tap again to change color' : 'Tap twice to change color');- hintParts.push('Use Reset to reset');+ hintParts.push(state.active ? 'Tap to change color' : 'Tap twice to change color'); } else {- hintParts.push('Double-click or press C to change color');- hintParts.push('Press R to reset');+ hintParts.push('Double-click to change color • Press C or R for shortcuts'); } ⋯ line 367 const showMode = state.active && state.colorMode > 0; el.modeIndicator.classList.toggle('visible', showMode);- if (showMode) el.modeIndicator.textContent = getColorModeName(state.colorMode);+ if (showMode) {+ const modeName = getColorModeName(state.colorMode);+ el.modeIndicator.textContent = modeName;+ } }; ⋯ line 414 }; - const handleKeyDown = (e) => {- const key = e.key.toLowerCase();- let handler;- if (key === ' ' || key === 'enter') handler = toggleActive;- else if (key === 'c') handler = cycleColor;- else if (key === 'r') handler = resetState;+ const keyHandlers = {+ ' ': toggleActive,+ 'enter': toggleActive,+ 'c': cycleColor,+ 'r': resetState+ }; + const handleKeyDown = (e) => {+ const handler = keyHandlers[e.key.toLowerCase()]; if (handler) { e.preventDefault();⋯ line 437 el.resetBtn.addEventListener('click', resetState); - storage.load();- applyState();+ const initApp = () => {+ storage.load();+ applyState();+ requestAnimationFrame(() => el.circle.focus());+ };+ if (document.readyState === 'loading') {- document.addEventListener('DOMContentLoaded', () => el.circle.focus());+ document.addEventListener('DOMContentLoaded', initApp); } else {- requestAnimationFrame(() => el.circle.focus());+ initApp(); } </script>
What the model said
Done! I've made several improvements to the HTML file: **Accessibility enhancements:** - Added proper ARIA labels and role attributes to controls - Improved semantic structure with `role="group"` for button controls - Enhanced live region attributes for better screen reader support **Code quality improvements:** - Simplified CSS transitions with consistent timing - Optimized the keydown handler using a cleaner object-based approach - Improved initialization logic with a dedicated `initApp` function - Better `will-change` properties for performance optimization **UX improvements:** - Updated hint text to be more concise and device-aware - Enhanced button styling with better touch targets (44px minimum height on mobile) - Improved button feedback with refined scale and shadow effects - Better mobile responsiveness with centered controls and flex wrapping **Performance enhancements:** - Added `@supports` queries for better fallback styling - Reduced unnecessary transitions on circle element - Optimized animation properties for better performance - Better handling of backdrop-filter support **Touch experience:** - Larger touch targets on mobile devices - Improved button padding and sizing for small screens - Flexbox alignment for responsive button layout