Persona 5-Inspired Geometric Wobble Hover
Not an exact recreation but a similar effect.
STATUS
<style>
.lab {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.menu {
display: flex;
flex-direction: column;
gap: 30px;
}
.menu-item {
position: relative;
font-family: 'Arial Black', sans-serif;
font-size: 3rem;
letter-spacing: 0.25em;
color: #fff;
cursor: pointer;
padding: 0.25em 0.5em;
z-index: 1;
}
.menu-item::before,
.menu-item::after {
content: '';
position: absolute;
inset: -12px;
z-index: 0;
transform: skewX(-14deg);
opacity: 0;
}
.menu-item::before {
background: #ff1b1b;
}
.menu-item::after {
background: #fff;
inset: -6px;
}
.menu-item.active::before {
opacity: 1;
animation:
liquidRed 2.6s ease-in-out infinite,
jitterRed 0.25s steps(1,end);
}
.menu-item.active::after {
opacity: 1;
animation:
liquidWhite 3s ease-in-out infinite,
jitterWhite 0.25s steps(1,end);
}
.menu-item.active {
animation: textPop 0.18s steps(1,end);
}
</style>
</head>
<body>
<div class="lab">
<div class="menu">
<div class="menu-item" onclick="select(this)">STATUS</div>
<div class="menu-item" onclick="select(this)">EQUIPMENT</div>
<div class="menu-item" onclick="select(this)">SKILLS</div>
<div class="menu-item" onclick="select(this)">MAP</div>
</div>
</div>
<script>
function select(el) {
document.querySelectorAll('.menu-item')
.forEach(i => i.classList.remove('active'));
el.classList.add('active');
}
</script>
Subtitle Hover Reveal
Secondary text appears beneath main text on hover.
ARCHIVE
Data Log
<div class="subtitle-demo">
<div class="subtitle-main">ARCHIVE</div>
<div class="subtitle-sub">Data Log</div>
</div>
</style>
.subtitle-demo {
text-align: center;
}
.subtitle-main {
font-size: 2.5rem;
cursor: pointer;
}
.subtitle-sub {
font-family: 'ArsenalBold';
font-size: 0.9rem;
letter-spacing: 0.15em;
margin-top: 6px;
background: #000;
padding: 0.3em 0.6em;
display: inline-block;
opacity: 0;
transform: translateY(-6px) scale(0.8);
transition: 0.25s cubic-bezier(0.34,1.56,0.64,1);
}
.subtitle-demo:hover .subtitle-sub {
opacity: 1;
transform: translateY(0) scale(1);
}
</style>
Shooting Stars Title
Make a wish or something I don't know.
SHOOTING STARS
<style>
.star-title-wrap {
position: relative;
padding: 80px 120px;
overflow: hidden;
}
.star-title {
font-size: 4.5rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: #f5f7ff;
position: relative;
z-index: 5;
text-shadow:
0 0 10px rgba(180,200,255,0.4),
0 0 30px rgba(120,160,255,0.25);
}
.star-field {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
}
.shooting-star {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
background: #fff;
box-shadow:
0 0 10px rgba(255,255,255,1),
0 0 24px rgba(200,220,255,0.9),
0 0 48px rgba(160,190,255,0.8);
opacity: 0;
transform: rotate(-45deg);
}
.shooting-star::after {
content: '';
position: absolute;
top: 50%;
left: 8px;
height: 4px;
width: 0;
transform: translateY(-50%);
border-radius: 999px;
background: linear-gradient(
90deg,
rgba(255,255,255,0),
rgba(200,220,255,0.8),
rgba(160,190,255,0.5),
rgba(120,160,255,0)
);
filter: blur(1.5px);
animation: tailGrow 1.5s ease-out infinite;
}
@keyframes tailGrow {
0% { width: 0; opacity: 0; }
20% { width: 140px; opacity: 1; }
45% { width: 260px; opacity: 1; }
75% { width: 160px; opacity: 0.8; }
100% { width: 40px; opacity: 0; }
}
@keyframes starShoot {
0% {
transform: translate(420px, -80px) rotate(-45deg);
opacity: 0;
}
10% { opacity: 1; }
100% {
transform: translate(-160px, 360px) rotate(-45deg);
opacity: 0;
}
}
</style>
<script>
const field = document.getElementById('starField');
function spawnShootingStar() {
const star = document.createElement('div');
star.className = 'shooting-star';
star.style.top = Math.random() * 60 - 10 + '%';
star.style.left = Math.random() * 100 - 10 + '%';
star.style.animation =
'starShoot 1.05s cubic-bezier(0.25, 0.8, 0.3, 1) forwards';
if (Math.random() > 0.85) {
star.style.filter = 'brightness(1.4)';
}
field.appendChild(star);
setTimeout(() => star.remove(), 1700);
}
setInterval(spawnShootingStar, 1600);
</script>