<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>macOS Style Dock</title>
<style>
:root {
--dock-height: 50px;
--dock-padding: 8px;
--icon-size: 48px;
--icon-hover-scale: 1.5;
/* Reduced from 2 to 1.5 */
--bounce-height: 10px;
--bg-color: rgba(255, 255, 255, 0.2);
--backdrop-blur: 12px;
--tooltip-delay: 0.4s;
--scale-duration: 0.3s;
--blur-default: 8px;
--blur-hover: 25px;
--label-delay: 150ms;
--label-duration: 200ms;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
background: linear-gradient(45deg, #252525, #837777);
display: flex;
justify-content: center;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
overflow: hidden;
}
.dock-container {
position: relative;
display: flex;
justify-content: center;
z-index: 100;
}
.dock {
display: flex;
gap: 33px;
height: calc(var(--dock-height) + var(--dock-padding) * 2);
padding: var(--dock-padding) 33px;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.08) 0%,
rgba(255, 255, 255, 0.05) 50%,
rgba(255, 255, 255, 0.03) 100%);
backdrop-filter: blur(30px) saturate(180%) brightness(1.1);
-webkit-backdrop-filter: blur(30px) saturate(180%) brightness(1.1);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
align-items: flex-end;
position: relative;
transition: all 0.3s ease;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.2),
0 15px 20px rgba(0, 0, 0, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.08);
opacity: 0;
animation: dockBackgroundEnter 0.8s cubic-bezier(0.23, 1, 0.32, 1) forwards;
will-change: transform, opacity, backdrop-filter, box-shadow;
}
.dock-item {
width: var(--icon-size);
height: var(--icon-size);
position: relative;
display: flex;
justify-content: center;
align-items: center;
transform-origin: bottom;
transition: transform 0.2s cubic-bezier(0.2, 0, 0, 1);
cursor: pointer;
will-change: transform, filter;
opacity: 0;
filter: brightness(1) contrast(1);
}
.dock-item:hover {
filter: brightness(1.1) contrast(1.1);
animation: itemHoverPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes itemHoverPop {
0% {
transform: scale(1) translateY(0);
}
50% {
transform: scale(1.1) translateY(-5px);
}
75% {
transform: scale(0.95) translateY(2px);
}
100% {
transform: scale(1) translateY(0);
}
}
.dock-item .icon-wrapper {
width: 100%;
height: 100%;
border-radius: 14px;
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 255, 255, 0.08) 40%,
rgba(255, 255, 255, 0.03) 100%);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: bottom;
will-change: transform, opacity, backdrop-filter, box-shadow, filter;
box-shadow:
0 8px 20px rgba(0, 0, 0, 0.12),
0 4px 8px rgba(0, 0, 0, 0.06),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12);
overflow: hidden;
position: relative;
animation: none;
}
.dock-item:hover .icon-wrapper {
animation: iconWrapperGlow 1.5s ease-in-out infinite alternate;
}
@keyframes iconWrapperGlow {
0% {
filter: brightness(1) saturate(1);
box-shadow:
0 8px 20px rgba(0, 0, 0, 0.12),
0 4px 8px rgba(0, 0, 0, 0.06),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12);
}
100% {
filter: brightness(1.2) saturate(1.1);
box-shadow:
0 12px 28px rgba(0, 0, 0, 0.15),
0 8px 12px rgba(0, 0, 0, 0.08),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2),
0 0 20px rgba(255, 255, 255, 0.1);
}
}
/* Enhanced glossy overlay */
.dock-item .icon-wrapper::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 55%;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.25) 0%,
rgba(255, 255, 255, 0.12) 45%,
rgba(255, 255, 255, 0.05) 65%,
rgba(255, 255, 255, 0.02) 100%);
border-radius: 14px 14px 24px 24px;
pointer-events: none;
opacity: 0.8;
transition: opacity 0.3s ease;
}
/* Enhanced bottom reflection */
.dock-item .icon-wrapper::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 45%;
background: linear-gradient(to top,
rgba(0, 0, 0, 0.15) 0%,
rgba(0, 0, 0, 0.1) 20%,
rgba(0, 0, 0, 0.05) 40%,
rgba(0, 0, 0, 0) 100%);
border-radius: 24px 24px 14px 14px;
pointer-events: none;
opacity: 0.7;
transition: opacity 0.3s ease;
}
/* Add side reflections */
.dock-item .icon-wrapper>div:first-child {
position: absolute;
left: 0;
top: 10%;
height: 80%;
width: 1px;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%);
opacity: 0.5;
}
.dock-item .icon-wrapper>div:last-child {
position: absolute;
right: 0;
top: 10%;
height: 80%;
width: 1px;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%);
opacity: 0.5;
}
/* Enhanced hover effects */
.dock-item:hover .icon-wrapper {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.25) 0%,
rgba(255, 255, 255, 0.15) 40%,
rgba(255, 255, 255, 0.08) 100%);
}
.dock-item:hover .icon-wrapper::before {
opacity: 1;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.35) 0%,
rgba(255, 255, 255, 0.18) 45%,
rgba(255, 255, 255, 0.08) 65%,
rgba(255, 255, 255, 0.03) 100%);
}
.dock-item:hover .icon-wrapper::after {
opacity: 0.9;
background: linear-gradient(to top,
rgba(0, 0, 0, 0.2) 0%,
rgba(0, 0, 0, 0.15) 20%,
rgba(0, 0, 0, 0.08) 40%,
rgba(0, 0, 0, 0) 100%);
}
.dock-icon {
width: 60%;
height: 60%;
object-fit: contain;
filter: brightness(0.95) contrast(0.95) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
opacity: 0.9;
position: relative;
z-index: 1;
}
.dock-label {
position: absolute;
top: -85px;
left: 50%;
background: linear-gradient(to bottom,
rgba(40, 40, 40, 0.95) 0%,
rgba(20, 20, 20, 0.95) 100%);
color: white;
padding: 8px 12px;
border-radius: 10px;
font-size: 12px;
font-weight: 400;
white-space: nowrap;
opacity: 0;
pointer-events: none;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.2),
inset 0 1px 1px rgba(255, 255, 255, 0.15),
inset 0 -1px 1px rgba(0, 0, 0, 0.3);
transform: translateX(-50%) translateY(10px);
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-webkit-font-smoothing: antialiased;
transform-style: preserve-3d;
transform-origin: bottom center;
will-change: transform, opacity, filter, background;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 1000;
letter-spacing: 0.3px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Enhanced glossy overlay */
.dock-label::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50%;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 255, 255, 0.05) 100%);
border-radius: 9px 9px 0 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
/* Clean arrow styling that follows label */
.dock-label::after {
content: '';
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 8px;
height: 8px;
background: inherit;
border-radius: 1px;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1);
border-right: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center center;
}
@keyframes labelClickBounce {
0% {
transform: translateX(-50%) translateY(0) scale(1);
background: linear-gradient(to bottom,
rgba(40, 40, 40, 0.95) 0%,
rgba(20, 20, 20, 0.95) 100%);
}
20% {
transform: translateX(-50%) translateY(15px) scale(0.95);
background: linear-gradient(to bottom,
rgba(50, 50, 50, 0.95) 0%,
rgba(30, 30, 30, 0.95) 100%);
}
60% {
transform: translateX(-50%) translateY(-5px) scale(1.05);
background: linear-gradient(to bottom,
rgba(45, 45, 45, 0.95) 0%,
rgba(25, 25, 25, 0.95) 100%);
}
100% {
transform: translateX(-50%) translateY(0) scale(1);
background: linear-gradient(to bottom,
rgba(40, 40, 40, 0.95) 0%,
rgba(20, 20, 20, 0.95) 100%);
}
}
.dock-item .dock-label.clicking {
animation: labelClickBounce 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.dock-item .dock-label.clicking::after {
animation: arrowClickBounce 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes arrowClickBounce {
0% {
transform: translateX(-50%) rotate(45deg) scale(1);
}
20% {
transform: translateX(-50%) rotate(45deg) scale(0.95);
}
60% {
transform: translateX(-50%) rotate(45deg) scale(1.05);
}
100% {
transform: translateX(-50%) rotate(45deg) scale(1);
}
}
.dock-item:hover .dock-label {
opacity: 1;
transform: translateX(-50%) translateY(0);
box-shadow:
0 4px 16px rgba(0, 0, 0, 0.3),
inset 0 1px 1px rgba(255, 255, 255, 0.2),
inset 0 -1px 1px rgba(0, 0, 0, 0.3);
}
.dock-item:active .icon-wrapper {
background: rgba(83, 81, 81, 0.9);
}
.dock-item:hover .icon-wrapper {
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.05) 100%);
backdrop-filter: blur(25px) brightness(1.2) saturate(1.2);
-webkit-backdrop-filter: blur(25px) brightness(1.2) saturate(1.2);
box-shadow:
0 15px 30px rgba(0, 0, 0, 0.2),
0 8px 12px rgba(0, 0, 0, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
.dock-item:hover .dock-icon {
filter: brightness(1.1) contrast(1.1) drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2));
opacity: 1;
transform: scale(1.1);
}
.dock-item:hover {
will-change: transform;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: rgba(0, 0, 0, 0.3);
}
}
@keyframes dockItemEnter {
0% {
opacity: 0;
transform: scale(0.3) translateY(50px);
filter: blur(10px);
}
40% {
transform: scale(1.2) translateY(-15px);
filter: blur(0px);
}
60% {
transform: scale(0.9) translateY(5px);
}
80% {
transform: scale(1.05) translateY(-3px);
}
90% {
transform: scale(0.98) translateY(1px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes dockBackgroundEnter {
0% {
opacity: 0;
transform: scale(0.95) translateY(20px);
backdrop-filter: blur(0px);
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
backdrop-filter: blur(30px);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.2),
0 15px 20px rgba(0, 0, 0, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.08);
}
}
@keyframes clickPop {
0% {
transform: scale(1);
}
30% {
transform: scale(0.95) translateY(2px);
}
60% {
transform: scale(1.02) translateY(-1px);
}
100% {
transform: scale(1);
}
}
@keyframes ripple {
0% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.3);
transform: scale(1);
}
70% {
box-shadow: 0 0 0 15px rgba(255, 255, 255, 0);
transform: scale(1.1);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
transform: scale(1);
}
}
@keyframes itemClickBounce {
0% {
transform: scale(1);
}
20% {
transform: scale(1.15);
}
40% {
transform: scale(0.94);
}
60% {
transform: scale(1.08);
}
75% {
transform: scale(0.97);
}
85% {
transform: scale(1.03);
}
92% {
transform: scale(0.99);
}
100% {
transform: scale(1);
}
}
@keyframes itemPulseGlow {
0% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4),
0 8px 20px rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 0.2);
}
50% {
box-shadow: 0 0 20px 5px rgba(255, 255, 255, 0.2),
0 8px 20px rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 0.15);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0),
0 8px 20px rgba(0, 0, 0, 0.12);
background: rgba(255, 255, 255, 0.12);
}
}
.dock-item .icon-wrapper.clicking {
animation:
itemClickBounce 0.8s cubic-bezier(0.36, 0, 0.66, 1.54) forwards,
itemPulseGlow 0.8s cubic-bezier(0.36, 0, 0.66, 1) forwards;
transform-origin: center;
}
@keyframes labelBounce {
0% {
transform: translateX(-50%) translateY(0) scale(1);
filter: blur(0px);
}
20% {
transform: translateX(-50%) translateY(25px) scale(0.85);
filter: blur(3px);
}
40% {
transform: translateX(-50%) translateY(-15px) scale(1.1);
filter: blur(1.5px);
}
60% {
transform: translateX(-50%) translateY(10px) scale(0.92);
filter: blur(1px);
}
75% {
transform: translateX(-50%) translateY(-6px) scale(1.05);
filter: blur(0.8px);
}
85% {
transform: translateX(-50%) translateY(4px) scale(0.98);
filter: blur(0.5px);
}
92% {
transform: translateX(-50%) translateY(-2px) scale(1.02);
filter: blur(0.3px);
}
100% {
transform: translateX(-50%) translateY(0) scale(1);
filter: blur(0px);
}
}
.dock-item .dock-label.clicking {
animation: labelBounce 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
will-change: transform, filter;
}
@keyframes labelClickExit {
0% {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1.05) rotateX(0);
filter: blur(0px);
}
30% {
opacity: 0.8;
transform: translateX(-50%) translateY(15px) scale(0.95) rotateX(-15deg);
filter: blur(2px);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(40px) scale(0.8) rotateX(-45deg);
filter: blur(6px);
}
}
@keyframes labelClickEnter {
0% {
opacity: 0;
transform: translateX(-50%) translateY(-40px) scale(0.8) rotateX(45deg);
filter: blur(6px);
}
40% {
opacity: 0.8;
transform: translateX(-50%) translateY(-10px) scale(1.15) rotateX(-10deg);
filter: blur(0px);
}
60% {
transform: translateX(-50%) translateY(5px) scale(0.95) rotateX(5deg);
}
80% {
transform: translateX(-50%) translateY(-3px) scale(1.08) rotateX(-3deg);
}
90% {
transform: translateX(-50%) translateY(1px) scale(1.02) rotateX(1deg);
}
100% {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1.05) rotateX(0);
filter: blur(0px);
}
}
.dock-item .dock-label {
transform-style: preserve-3d;
perspective: 800px;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.2s ease;
filter: blur(0px);
will-change: transform, opacity, filter;
}
.dock-item .dock-label.clicking-exit {
animation: labelClickExit 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.dock-item .dock-label.clicking-enter {
animation: labelClickEnter 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
.dock-item::after {
content: '';
position: absolute;
right: -12px;
top: 15%;
height: 70%;
width: 1px;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.301) 30%,
rgba(255, 255, 255, 0.568) 50%,
rgba(255, 255, 255, 0.349) 70%,
rgba(255, 255, 255, 0) 100%);
pointer-events: none;
opacity: 0.3;
/* Default visibility */
transition: opacity 0.3s ease;
}
.dock-item:hover::after,
.dock-item:hover+.dock-item::after {
opacity: 0.15;
/* Reduced opacity on hover for subtlety */
}
/* Hide the last item's separator */
.dock-item:last-child::after {
display: none;
}
@keyframes hover-glow {
0% {
filter: brightness(1) saturate(1);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.2),
0 8px 15px rgba(255, 255, 255, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
50% {
filter: brightness(1.3) saturate(1.2);
box-shadow:
0 20px 45px rgba(255, 255, 255, 0.3),
0 12px 25px rgba(255, 255, 255, 0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3);
}
100% {
filter: brightness(1) saturate(1);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.2),
0 8px 15px rgba(255, 255, 255, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
}
.hover-glow {
animation: hover-glow 2s ease-in-out infinite;
}
.hover-indicator {
position: absolute;
bottom: var(--dock-padding);
width: var(--icon-size);
height: var(--icon-size);
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 255, 255, 0.08) 50%,
rgba(255, 255, 255, 0.05) 100%);
border-radius: 14px;
transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
pointer-events: none;
opacity: 0;
z-index: 0;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow:
0 8px 20px rgba(255, 255, 255, 0.15),
0 4px 8px rgba(255, 255, 255, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
transform-origin: center bottom;
}
.hover-indicator.active {
opacity: 1;
}
.hover-indicator.moving {
transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1),
width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Add SVG animations for each icon */
@keyframes finderIconAnimation {
0% {
stroke-dasharray: 60;
stroke-dashoffset: 60;
stroke-width: 1.5;
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
50% {
stroke-dashoffset: 0;
stroke-width: 2;
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.6));
}
100% {
stroke-dasharray: 60;
stroke-dashoffset: -60;
stroke-width: 1.5;
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
}
@keyframes browserIconSpin {
0% {
stroke-dasharray: 120;
stroke-dashoffset: 120;
transform: rotate(0deg);
}
50% {
stroke-dashoffset: 0;
transform: rotate(180deg);
}
100% {
stroke-dasharray: 120;
stroke-dashoffset: -120;
transform: rotate(360deg);
}
}
@keyframes chatIconPulse {
0% {
stroke-dasharray: 80;
stroke-dashoffset: 80;
transform: scale(0.9);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
50% {
stroke-dashoffset: 0;
transform: scale(1.1);
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.6));
}
100% {
stroke-dasharray: 80;
stroke-dashoffset: -80;
transform: scale(0.9);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
}
@keyframes terminalCursor {
0% {
stroke-dasharray: 40;
stroke-dashoffset: 40;
transform: translateX(0);
}
25% {
stroke-dashoffset: 0;
transform: translateX(2px);
}
50% {
stroke-dashoffset: -40;
transform: translateX(0);
}
75% {
stroke-dashoffset: -80;
transform: translateX(-2px);
}
100% {
stroke-dasharray: 40;
stroke-dashoffset: -120;
transform: translateX(0);
}
}
@keyframes codeIconFlip {
0% {
stroke-dasharray: 100;
stroke-dashoffset: 100;
transform: rotateY(0deg) scale(1);
}
50% {
stroke-dashoffset: 0;
transform: rotateY(180deg) scale(1.1);
}
100% {
stroke-dasharray: 100;
stroke-dashoffset: -100;
transform: rotateY(360deg) scale(1);
}
}
@keyframes settingsIconRotate {
0% {
stroke-dasharray: 150;
stroke-dashoffset: 150;
transform: rotate(0deg);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
50% {
stroke-dashoffset: 0;
transform: rotate(180deg);
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.6));
}
100% {
stroke-dasharray: 150;
stroke-dashoffset: -150;
transform: rotate(360deg);
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.3));
}
}
/* Prepare SVG paths for animation */
.dock-icon path,
.dock-icon circle {
stroke-linecap: round;
stroke-linejoin: round;
transition: all 0.3s ease;
}
/* Enhanced hover states for SVG icons */
.dock-item:hover .dock-icon {
filter: brightness(1.2) drop-shadow(0 0 8px rgba(255, 255, 255, 0.4));
}
.dock-item:hover .dock-icon path,
.dock-item:hover .dock-icon circle {
stroke-width: 2;
}
/* Specific icon hover effects */
.dock-item[data-icon="finder"]:hover .dock-icon {
animation: finderIconAnimation 2s ease-in-out infinite;
}
.dock-item[data-icon="browser"]:hover .dock-icon {
animation: browserIconSpin 3s ease-in-out infinite;
}
.dock-item[data-icon="chat"]:hover .dock-icon {
animation: chatIconPulse 1.5s ease-in-out infinite;
}
.dock-item[data-icon="terminal"]:hover .dock-icon {
animation: terminalCursor 1.5s ease-in-out infinite;
}
.dock-item[data-icon="code"]:hover .dock-icon {
animation: codeIconFlip 2.5s ease-in-out infinite;
transform-origin: center center;
transform-style: preserve-3d;
backface-visibility: visible;
}
.dock-item[data-icon="settings"]:hover .dock-icon {
animation: settingsIconRotate 3s ease-in-out infinite;
transform-origin: center center;
}
/* Enhanced glow effect for focused item */
.dock-item .icon-wrapper.focused-glow {
animation: focusedGlow 1.5s ease-in-out infinite !important;
z-index: 2;
}
@keyframes focusedGlow {
0% {
filter: brightness(1.2) saturate(1.2);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.3),
0 8px 15px rgba(255, 255, 255, 0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3),
0 0 20px rgba(255, 255, 255, 0.2);
}
50% {
filter: brightness(1.4) saturate(1.4);
box-shadow:
0 20px 45px rgba(255, 255, 255, 0.4),
0 12px 25px rgba(255, 255, 255, 0.3),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.4),
0 0 30px rgba(255, 255, 255, 0.3);
}
100% {
filter: brightness(1.2) saturate(1.2);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.3),
0 8px 15px rgba(255, 255, 255, 0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3),
0 0 20px rgba(255, 255, 255, 0.2);
}
}
/* SVG path animations */
.dock-item:hover .dock-icon path,
.dock-item:hover .dock-icon circle {
stroke-dasharray: 100;
animation: svgDraw 1.5s ease-in-out infinite;
}
@keyframes svgDraw {
0% {
stroke-dashoffset: 100;
stroke-width: 1.5;
}
50% {
stroke-dashoffset: 0;
stroke-width: 2;
}
100% {
stroke-dashoffset: -100;
stroke-width: 1.5;
}
}
</style>
</head>
<body>
<div class="dock-container">
<div class="dock">
<!-- Finder -->
<div class="dock-item" data-icon="finder">
<span class="dock-label">Finder</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
<!-- Browser -->
<div class="dock-item" data-icon="browser">
<span class="dock-label">Browser</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<circle cx="12" cy="12" r="10" />
<path
d="M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
<!-- Chat -->
<div class="dock-item" data-icon="chat">
<span class="dock-label">Chat</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<path
d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
<!-- Terminal -->
<div class="dock-item" data-icon="terminal">
<span class="dock-label">Terminal</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<path d="M4 17l6-6-6-6M12 19h8" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
<!-- Code -->
<div class="dock-item" data-icon="code">
<span class="dock-label">Code</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<path d="M16 18l6-6-6-6M8 6l-6 6 6 6" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
<!-- Settings -->
<div class="dock-item" data-icon="settings">
<span class="dock-label">Settings</span>
<div class="icon-wrapper">
<div class="side-reflection-left"></div>
<svg class="dock-icon" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)"
stroke-width="1.5">
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z" />
<path
d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" />
</svg>
<div class="side-reflection-right"></div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const dock = document.querySelector('.dock');
const dockItems = document.querySelectorAll('.dock-item');
function calculateScale(distance, proximity = 150) {
const maxScale = 1.8;
const falloff = 0.5;
if (distance > proximity) return 1;
const normalizedDistance = distance / proximity;
const waveEffect = Math.cos(normalizedDistance * Math.PI * 0.5) * 0.7;
const scale = 1 + (maxScale - 1) * Math.pow(1 - normalizedDistance, falloff) *
(0.6 + 0.4 * waveEffect);
return Math.max(1, Math.min(maxScale, scale));
}
function updateDockItems(e) {
const mouseX = e.clientX;
const mouseY = e.clientY;
const dockRect = dock.getBoundingClientRect();
// Check if mouse is completely outside the dock area
const padding = 50;
if (mouseY < dockRect.top - padding || mouseY > dockRect.bottom + padding) {
resetDockItems();
resetHoverIndicator();
return;
}
let currentHoveredItem = null;
let maxScale = 0;
dockItems.forEach(item => {
const itemRect = item.getBoundingClientRect();
const itemCenter = itemRect.left + itemRect.width / 2;
const distance = Math.abs(mouseX - itemCenter);
const scale = calculateScale(distance);
if (scale > maxScale) {
maxScale = scale;
currentHoveredItem = item;
}
});
// Update hover indicator
if (currentHoveredItem && maxScale > 1) {
updateHoverIndicator(currentHoveredItem, maxScale);
} else {
resetHoverIndicator();
}
// Enhanced hover effect with dramatic scaling and glow
requestAnimationFrame(() => {
dockItems.forEach(item => {
const itemRect = item.getBoundingClientRect();
const itemCenter = itemRect.left + itemRect.width / 2;
const distance = Math.abs(mouseX - itemCenter);
let scale = calculateScale(distance);
const isHovered = item === currentHoveredItem;
// Reduce scale for non-hovered items
if (!isHovered) {
scale = 1 + (scale - 1) * 0.7;
}
// Enhanced scale transform with dramatic lift effect for hovered item
const liftAmount = isHovered ? -25 : Math.max(0, (scale - 1) * -10);
const scaleTransform = `scale(${scale})`;
const translateTransform = `translate3d(0, ${liftAmount}px, 0)`;
// Add slight rotation based on mouse position for hovered item
const rotationAmount = isHovered ?
((mouseX - itemCenter) / itemRect.width) * 5 : 0;
item.style.transform = `${scaleTransform} ${translateTransform} rotateZ(${rotationAmount}deg)`;
item.style.zIndex = isHovered ? '10' : '1';
const iconWrapper = item.querySelector('.icon-wrapper');
const label = item.querySelector('.dock-label');
// Dramatic glow and hover effects
if (scale > 1) {
const normalizedScale = (scale - 1) / 0.8;
const intensity = isHovered ? 2 : normalizedScale * 0.7;
Object.assign(iconWrapper.style, {
transform: `scale(${scale * 0.95}) translate3d(0, 0, 0)`,
background: `linear-gradient(135deg,
rgba(255, 255, 255, ${0.3 * intensity}) 0%,
rgba(255, 255, 255, ${0.2 * intensity}) 50%,
rgba(255, 255, 255, ${0.1 * intensity}) 100%
)`,
backdropFilter: `blur(${lerp(8, 35, normalizedScale)}px) brightness(${lerp(1, 1.4, normalizedScale)}) saturate(${lerp(1, 1.5, normalizedScale)})`,
'-webkit-backdrop-filter': `blur(${lerp(8, 35, normalizedScale)}px) brightness(${lerp(1, 1.4, normalizedScale)}) saturate(${lerp(1, 1.5, normalizedScale)})`,
boxShadow: isHovered ?
`
0 ${lerp(15, 35, normalizedScale)}px ${lerp(30, 60, normalizedScale)}px rgba(255, 255, 255, ${lerp(0.2, 0.4, normalizedScale)}),
0 ${lerp(8, 20, normalizedScale)}px ${lerp(15, 30, normalizedScale)}px rgba(255, 255, 255, ${lerp(0.1, 0.3, normalizedScale)}),
inset 0 0 0 0.5px rgba(255, 255, 255, ${lerp(0.2, 0.4, normalizedScale)})
` :
`
0 ${lerp(8, 20, normalizedScale)}px ${lerp(20, 40, normalizedScale)}px rgba(255, 255, 255, ${lerp(0.1, 0.2, normalizedScale) * 0.7}),
0 ${lerp(4, 10, normalizedScale)}px ${lerp(8, 15, normalizedScale)}px rgba(255, 255, 255, ${lerp(0.05, 0.15, normalizedScale) * 0.7}),
inset 0 0 0 0.5px rgba(255, 255, 255, ${lerp(0.12, 0.25, normalizedScale) * 0.7})
`
});
// Coordinated label movement with icon
if (isHovered && label.style.opacity === '1') {
label.style.transform = `translate3d(calc(-50% + ${rotationAmount * 2}px), ${liftAmount * 0.7}px, 0) rotateZ(${rotationAmount * 0.5}deg)`;
}
}
// Handle label visibility with the existing animations
const itemFullRect = item.getBoundingClientRect();
const currentScale = parseFloat(item.style.transform.match(/scale\((.*?)\)/)?.[1] || 1);
const scaledWidth = itemFullRect.width * (currentScale || 1);
const scaledHeight = itemFullRect.height * (currentScale || 1);
const extraWidth = (scaledWidth - itemFullRect.width) / 2;
const extraHeight = (scaledHeight - itemFullRect.height) / 2;
const isMouseOverItem =
mouseX >= (itemFullRect.left - extraWidth) &&
mouseX <= (itemFullRect.right + extraWidth) &&
mouseY >= (itemFullRect.top - extraHeight) &&
mouseY <= (itemFullRect.bottom + extraHeight);
if (isMouseOverItem) {
if (label.style.opacity === '0' || !label.style.opacity) {
label.classList.remove('clicking-exit');
label.classList.add('clicking-enter');
setTimeout(() => {
label.classList.remove('clicking-enter');
}, 550);
}
const relativeX = (mouseX - (itemFullRect.left - extraWidth)) / scaledWidth;
const rotation = (relativeX - 0.5) * 15;
const horizontalShift = (relativeX - 0.5) * 8;
label.style.transform = `translate3d(calc(-50% + ${horizontalShift}px), 0, 0) rotateZ(${rotation}deg)`;
label.style.opacity = '1';
} else {
if (label.style.opacity === '1') {
label.classList.remove('clicking-enter');
label.classList.add('clicking-exit');
label.style.transform = 'translate3d(-50%, 0, 0) rotateZ(0deg)';
setTimeout(() => {
label.classList.remove('clicking-exit');
label.style.opacity = '0';
// Remove transition after animation
item.style.transition = '';
iconWrapper.style.transition = '';
}, 350);
}
}
// Update icon animations based on hover state
if (isHovered) {
const icon = item.querySelector('.dock-icon');
const iconWrapper = item.querySelector('.icon-wrapper');
// Remove previous animation classes
iconWrapper.classList.remove('hover-glow');
iconWrapper.classList.add('focused-glow');
// Add specific animations based on icon type
if (item.querySelector('.dock-label').textContent === 'Finder') {
icon.style.animation = 'finderIconAnimation 2s ease-in-out infinite';
} else if (item.querySelector('.dock-label').textContent === 'Browser') {
icon.style.animation = 'browserIconSpin 3s linear infinite';
} else if (item.querySelector('.dock-label').textContent === 'Chat') {
icon.style.animation = 'chatIconPulse 1s ease-in-out infinite';
} else if (item.querySelector('.dock-label').textContent === 'Terminal') {
icon.style.animation = 'terminalCursor 1s step-end infinite';
} else if (item.querySelector('.dock-label').textContent === 'Code') {
icon.style.animation = 'codeIconFlip 2s ease-in-out infinite';
} else if (item.querySelector('.dock-label').textContent === 'Settings') {
icon.style.animation = 'settingsIconRotate 3s ease-in-out infinite';
}
} else {
// Reset animations when not hovered
const icon = item.querySelector('.dock-icon');
const iconWrapper = item.querySelector('.icon-wrapper');
icon.style.animation = 'none';
iconWrapper.classList.remove('focused-glow');
}
});
});
}
// Add linear interpolation helper
function lerp(start, end, t) {
return start * (1 - t) + end * t;
}
// Performance hack: Use passive event listener with RAF for smooth animation
let rafId = null;
dock.addEventListener('mousemove', (e) => {
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => updateDockItems(e));
}, { passive: true });
// Optimize mouseleave handler with smooth reset
dock.addEventListener('mouseleave', (e) => {
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
resetDockItems();
}, { passive: true });
// Add click handler to prevent focus
document.addEventListener('click', (e) => {
if (!e.target.closest('.dock')) {
// Remove focus from dock items
dockItems.forEach(item => {
item.blur();
});
}
});
// Prevent focus on dock items
dockItems.forEach(item => {
item.addEventListener('mousedown', (e) => {
e.preventDefault(); // Prevent default focus behavior
});
});
// Track mouse position for ambient effect
document.addEventListener('mousemove', (e) => {
const container = document.querySelector('.dock-container');
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
container.style.setProperty('--x', `${x}%`);
container.style.setProperty('--y', `${y}%`);
});
// Add sequential loading animation
function animateDockItems() {
dock.style.opacity = '1';
// Add a slight delay before starting icon animations
setTimeout(() => {
dockItems.forEach((item, index) => {
// More organic random delay
const randomDelay = Math.random() * 60 - 30; // ±30ms variation
setTimeout(() => {
item.style.animation = `dockItemEnter 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards`;
// Enhanced icon wrapper animation
const iconWrapper = item.querySelector('.icon-wrapper');
iconWrapper.style.transition = 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)';
iconWrapper.style.transform = 'scale(1)';
}, 180 + (index * 80) + randomDelay); // Increased spacing between items
});
}, 250); // Slightly longer wait for background
}
// Start the animation sequence
requestAnimationFrame(() => {
animateDockItems();
});
// Enhanced click animation with smoother transitions
dockItems.forEach(item => {
const iconWrapper = item.querySelector('.icon-wrapper');
const label = item.querySelector('.dock-label');
item.addEventListener('mousedown', () => {
iconWrapper.classList.add('clicking');
if (label.style.opacity !== '0') {
label.classList.add('clicking');
}
// Remove classes after animation completes
setTimeout(() => {
iconWrapper.classList.remove('clicking');
label.classList.remove('clicking');
}, 1200); // Match our new bounce animation duration
});
});
function resetDockItems() {
requestAnimationFrame(() => {
dockItems.forEach(item => {
// Smooth transition back to original state
item.style.transition = 'transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)';
item.style.transform = 'scale(1) translate3d(0, 0, 0)';
const iconWrapper = item.querySelector('.icon-wrapper');
iconWrapper.style.transition = 'all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)';
Object.assign(iconWrapper.style, {
transform: 'scale(1) translate3d(0, 0, 0)',
background: `linear-gradient(
135deg,
rgba(255, 255, 255, 0.12) 0%,
rgba(255, 255, 255, 0.06) 50%,
rgba(255, 255, 255, 0.02) 100%
)`,
backdropFilter: 'blur(8px)',
'-webkit-backdrop-filter': 'blur(8px)',
boxShadow: `
0 8px 20px rgba(0, 0, 0, 0.12),
0 4px 8px rgba(0, 0, 0, 0.06),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12)
`
});
const label = item.querySelector('.dock-label');
if (label.style.opacity === '1') {
label.classList.remove('clicking-enter');
label.classList.add('clicking-exit');
label.style.transform = 'translate3d(-50%, 0, 0) rotateZ(0deg)';
setTimeout(() => {
label.classList.remove('clicking-exit');
label.style.opacity = '0';
// Remove transition after animation
item.style.transition = '';
iconWrapper.style.transition = '';
}, 350);
} else {
// Remove transition immediately if label is already hidden
setTimeout(() => {
item.style.transition = '';
iconWrapper.style.transition = '';
}, 400);
}
});
});
}
// Add new hover glow animation
const style = document.createElement('style');
style.textContent = `
@keyframes hover-glow {
0% {
filter: brightness(1) saturate(1);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.2),
0 8px 15px rgba(255, 255, 255, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
50% {
filter: brightness(1.3) saturate(1.2);
box-shadow:
0 20px 45px rgba(255, 255, 255, 0.3),
0 12px 25px rgba(255, 255, 255, 0.2),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.3);
}
100% {
filter: brightness(1) saturate(1);
box-shadow:
0 15px 35px rgba(255, 255, 255, 0.2),
0 8px 15px rgba(255, 255, 255, 0.1),
inset 0 0 0 0.5px rgba(255, 255, 255, 0.2);
}
}
.hover-glow {
animation: hover-glow 2s ease-in-out infinite;
}
`;
document.head.appendChild(style);
function updateHoverIndicator(item, scale) {
if (!item) return;
const itemRect = item.getBoundingClientRect();
const dockRect = dock.getBoundingClientRect();
const width = itemRect.width * scale;
requestAnimationFrame(() => {
hoverIndicator.classList.add('active');
if (!hoverIndicator.classList.contains('moving')) {
hoverIndicator.classList.add('moving');
}
hoverIndicator.style.transform = `translateX(${itemRect.left - dockRect.left}px) scale(${scale})`;
hoverIndicator.style.width = `${itemRect.width}px`;
});
}
function resetHoverIndicator() {
hoverIndicator.classList.remove('active', 'moving');
}
});
</script>
</body>
</html>