Custom Code Experiment •
3D Tiles
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>3D Hover Tiles</title>
<style>
* {
box-sizing: border-box;
}
body {
background: #000000;
overflow: hidden;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: flex-start; /* Changed from center to flex-start */
align-items: center;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
touch-action: manipulation;
}
p {
font-family: Arial, Helvetica, sans-serif;
font-size: clamp(1em, 4vw, 2em);
position: absolute;
top: 1em;
left: 1em;
margin: 0;
color: #6A8EAE;
z-index: 10;
text-shadow: 0 0 10px rgba(106, 142, 174, 0.5);
padding: 0 1rem;
}
#Container {
perspective: 1000px;
width: 100%;
display: flex;
justify-content: center;
align-items: flex-start; /* Changed from center to flex-start */
flex-grow: 1;
padding: 1rem;
overflow: hidden;
margin-top: 0; /* Ensure no top margin */
}
#Tiles {
width: 90vmin;
height: 90vmin;
max-width: 630px;
max-height: 630px;
position: relative;
transform-style: preserve-3d;
/* Reduced rotation for mobile to prevent distortion */
transform: rotateX(65deg) rotateY(0deg) rotateZ(45deg);
margin-top: 0; /* Ensure no top margin */
}
.title {
width: calc(100% / 3 - 6px);
height: calc(100% / 3 - 6px);
position: absolute;
cursor: pointer;
transform-style: preserve-3d;
touch-action: manipulation;
}
/* Position each tile in the 3x3 grid */
.title:nth-child(1) { top: 0; left: 0; }
.title:nth-child(2) { top: 0; left: calc(100% / 3); }
.title:nth-child(3) { top: 0; left: calc(200% / 3); }
.title:nth-child(4) { top: calc(100% / 3); left: 0; }
.title:nth-child(5) { top: calc(100% / 3); left: calc(100% / 3); }
.title:nth-child(6) { top: calc(100% / 3); left: calc(200% / 3); }
.title:nth-child(7) { top: calc(200% / 3); left: 0; }
.title:nth-child(8) { top: calc(200% / 3); left: calc(100% / 3); }
.title:nth-child(9) { top: calc(200% / 3); left: calc(200% / 3); }
.tile-face {
position: absolute;
width: 100%;
height: 100%;
background: #4A6FA5;
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
border-radius: 2px;
/* Mobile-friendly hover/active state */
cursor: pointer;
}
.tile-face::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 50%);
pointer-events: none;
border-radius: 2px;
}
/* Hover effect for desktop */
@media (hover: hover) and (pointer: fine) {
.title:hover .tile-face {
background: #E76F51;
transform: translateZ(40px);
box-shadow: 15px 15px 15px rgba(0,0,0,0.5);
}
}
/* Active/touch effect for mobile */
.title.active .tile-face,
.title:active .tile-face {
background: #E76F51;
transform: translateZ(20px);
box-shadow: 10px 10px 10px rgba(0,0,0,0.4);
transition: transform 0.15s ease, background-color 0.15s ease;
}
.instructions {
position: absolute;
bottom: 1em;
right: 1em;
color: #6A8EAE;
font-family: Arial, Helvetica, sans-serif;
font-size: clamp(0.8em, 3vw, 1em);
text-align: right;
max-width: min(300px, 90vw);
line-height: 1.5;
text-shadow: 0 0 5px rgba(106, 142, 174, 0.5);
padding: 0 1rem;
}
/* Mobile-specific optimizations */
@media (max-width: 768px) {
body {
justify-content: flex-start; /* Already set, but ensures mobile */
padding-top: 0.5vh; /* Minimal top padding */
margin-top: 0;
}
#Container {
perspective: 600px;
padding: 0.25rem 0.5rem; /* Greatly reduced padding */
padding-top: 0; /* No top padding */
margin-top: 0; /* No top margin */
align-items: flex-start; /* Align to top */
min-height: auto; /* Don't force full height */
height: auto;
flex-grow: 0; /* Don't take up all space */
}
#Tiles {
width: 85vmin; /* Slightly smaller on mobile */
height: 85vmin;
transform: rotateX(60deg) rotateY(0deg) rotateZ(45deg);
margin-top: 2vh; /* Minimal top margin */
}
p {
top: 0.25em; /* Much closer to top */
left: 0.25em;
font-size: clamp(0.8em, 4vw, 1.2em); /* Slightly smaller */
padding: 0 0.5rem; /* Reduced padding */
}
.instructions {
bottom: 0.25em; /* Closer to bottom */
right: 0.25em;
font-size: clamp(0.7em, 3.5vw, 0.9em);
padding: 0 0.5rem; /* Reduced padding */
}
.title {
width: calc(100% / 3 - 4px);
height: calc(100% / 3 - 4px);
}
}
@media (max-width: 480px) {
body {
padding-top: 0.25vh; /* Even smaller padding */
justify-content: flex-start;
}
#Container {
perspective: 400px;
padding: 0.125rem 0.25rem; /* Minimal padding */
padding-top: 0;
margin-top: 0;
height: auto;
}
#Tiles {
transform: rotateX(55deg) rotateY(0deg) rotateZ(45deg);
margin-top: 1vh; /* Minimal top margin */
width: 80vmin; /* Even smaller on very small screens */
height: 80vmin;
}
body {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
/* Reduce 3D effect on very small screens */
.title.active .tile-face,
.title:active .tile-face {
transform: translateZ(15px);
box-shadow: 5px 5px 8px rgba(0,0,0,0.3);
}
@media (hover: hover) and (pointer: fine) {
.title:hover .tile-face {
transform: translateZ(25px);
box-shadow: 8px 8px 12px rgba(0,0,0,0.4);
}
}
p {
top: 0.125em; /* Minimal distance from top */
left: 0.125em;
font-size: clamp(0.7em, 3.5vw, 1em);
}
}
/* Landscape orientation optimization */
@media (max-height: 500px) and (orientation: landscape) {
body {
padding-top: 0;
justify-content: flex-start;
}
#Container {
padding: 0.125rem;
padding-top: 0;
margin-top: 0;
}
#Tiles {
width: 70vmin; /* Smaller in landscape */
height: 70vmin;
transform: rotateX(50deg) rotateY(0deg) rotateZ(45deg);
margin-top: 0.5vh; /* Very small top margin */
}
p {
font-size: 0.9em;
top: 0.125em; /* Minimal top spacing */
left: 0.125em;
}
.instructions {
display: none;
}
}
/* Very small height devices */
@media (max-height: 600px) {
body {
padding-top: 0;
}
#Container {
padding-top: 0;
margin-top: 0;
}
#Tiles {
margin-top: 1vh;
}
}
/* High DPI screens */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.tile-face::after {
background: linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0) 50%);
}
}
</style>
</head>
<body>
<div id="Container">
<div id="Tiles">
<div class="title" tabindex="0" role="button" aria-label="Tile 1"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 2"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 3"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 4"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 5"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 6"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 7"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 8"><div class="tile-face"></div></div>
<div class="title" tabindex="0" role="button" aria-label="Tile 9"><div class="tile-face"></div></div>
</div>
</div>
<script>
// Mobile-friendly touch handling
document.querySelectorAll('.title').forEach(tile => {
let isTouching = false;
let touchTimer = null;
// Touch events
tile.addEventListener('touchstart', function(e) {
e.preventDefault();
isTouching = true;
this.classList.add('active');
// Clear any existing timer
if (touchTimer) clearTimeout(touchTimer);
// Set timer to remove active class if touch is held
touchTimer = setTimeout(() => {
if (isTouching) {
this.classList.remove('active');
}
}, 500);
}, { passive: false });
tile.addEventListener('touchend', function(e) {
e.preventDefault();
isTouching = false;
this.classList.remove('active');
if (touchTimer) {
clearTimeout(touchTimer);
touchTimer = null;
}
// Trigger click effect on touch end
triggerClickEffect(this);
}, { passive: false });
tile.addEventListener('touchcancel', function() {
isTouching = false;
this.classList.remove('active');
if (touchTimer) {
clearTimeout(touchTimer);
touchTimer = null;
}
});
// Click event (for both mouse and touch)
tile.addEventListener('click', function(e) {
// For mouse clicks, trigger effect
if (!isTouching) {
triggerClickEffect(this);
}
});
// Mouse events
tile.addEventListener('mousedown', function() {
this.classList.add('active');
});
tile.addEventListener('mouseup', function() {
this.classList.remove('active');
});
tile.addEventListener('mouseleave', function() {
this.classList.remove('active');
});
});
function triggerClickEffect(element) {
const colors = ['#E76F51', '#2A9D8F', '#E9C46A', '#264653', '#F4A261'];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
const tileFace = element.querySelector('.tile-face');
// Store original styles
const originalBg = tileFace.style.backgroundColor;
const originalTransform = tileFace.style.transform;
const originalBoxShadow = tileFace.style.boxShadow;
// Flash effect with mobile-optimized values
const isMobile = window.innerWidth <= 768;
const liftAmount = isMobile ? '25px' : '60px';
const scaleAmount = isMobile ? '1.02' : '1.05';
tileFace.style.backgroundColor = randomColor;
tileFace.style.transform = `translateZ(${liftAmount}) scale(${scaleAmount})`;
tileFace.style.boxShadow = isMobile
? '15px 15px 15px rgba(0,0,0,0.6)'
: '40px 40px 30px rgba(0,0,0,0.8)';
// Reset after animation
setTimeout(() => {
tileFace.style.backgroundColor = originalBg;
tileFace.style.transform = originalTransform;
tileFace.style.boxShadow = originalBoxShadow;
}, 300);
}
// Keyboard navigation for accessibility
document.addEventListener('keydown', (e) => {
const tiles = Array.from(document.querySelectorAll('.title'));
const currentIndex = tiles.findIndex(tile => tile === document.activeElement);
if (e.key === 'Tab' && !e.shiftKey && currentIndex === tiles.length - 1) {
e.preventDefault();
tiles[0].focus();
} else if (e.key === 'Tab' && e.shiftKey && currentIndex <= 0) {
e.preventDefault();
tiles[tiles.length - 1].focus();
} else if (e.key === 'Enter' && document.activeElement.classList.contains('title')) {
document.activeElement.click();
}
});
// Make tiles focusable for accessibility
document.querySelectorAll('.title').forEach(tile => {
tile.setAttribute('tabindex', '0');
tile.addEventListener('focus', function() {
this.querySelector('.tile-face').style.outline = '2px solid #6A8EAE';
this.querySelector('.tile-face').style.outlineOffset = '3px';
});
tile.addEventListener('blur', function() {
this.querySelector('.tile-face').style.outline = '';
});
});
// Prevent zoom on double tap for mobile
let lastTouchEnd = 0;
document.addEventListener('touchend', function(event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
</script>
</body>
</html>