From a7c50c5fd0f7f1d843bba724748f685c0059459b Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Wed, 9 Sep 2026 19:42:50 -0400 Subject: [PATCH] feat(campaigns): add one-shot glitch hover effects for official campaign cards --- src/app/(frontend)/styles.css | 289 ++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/src/app/(frontend)/styles.css b/src/app/(frontend)/styles.css index 1d45424..ffa88c5 100644 --- a/src/app/(frontend)/styles.css +++ b/src/app/(frontend)/styles.css @@ -841,3 +841,292 @@ .wiki-markdown .hljs-strong { font-weight: 600; } + +/* Glitch hover effect for official campaign card titles: fires once (~1s) on hover, then reverts. */ +.glitch-hover { + position: relative; +} + +.glitch-ghost { + position: absolute; + inset: 0; + text-wrap: balance; + opacity: 0; + pointer-events: none; +} + +.glitch-shard-a { + color: #22d3ee; +} + +.glitch-shard-b { + color: #f43f5e; +} + +.glitch-shard-c { + color: #ffffff; +} + +.group:hover .glitch-hover { + animation: glitch-jitter 1s ease-out 1; +} + +.group:hover .glitch-shard-a { + animation: glitch-shard-a 1s ease-out 1; +} + +.group:hover .glitch-shard-b { + animation: glitch-shard-b 1s ease-out 1; +} + +.group:hover .glitch-shard-c { + animation: glitch-shard-c 1s ease-out 1; +} + +@keyframes glitch-jitter { + 0% { + transform: translate(3px, -2px) skewX(2deg); + text-shadow: + -4px 0 rgba(34, 211, 238, 0.7), + 4px 0 rgba(244, 63, 94, 0.7); + animation-timing-function: steps(1, end); + } + 5% { + transform: translate(-3px, 2px) skewX(-2deg); + text-shadow: + 3px 0 rgba(34, 211, 238, 0.7), + -3px 0 rgba(244, 63, 94, 0.7); + animation-timing-function: steps(1, end); + } + 10% { + transform: translate(2px, -1px) skewX(1deg); + text-shadow: + -2px 0 rgba(34, 211, 238, 0.6), + 2px 0 rgba(244, 63, 94, 0.6); + animation-timing-function: steps(1, end); + } + 15% { + transform: translate(-1px, 1px); + text-shadow: + 1px 0 rgba(34, 211, 238, 0.5), + -1px 0 rgba(244, 63, 94, 0.5); + animation-timing-function: ease-out; + } + 25% { + transform: translate(0, 0) skewX(0deg); + text-shadow: none; + } + 100% { + transform: translate(0, 0) skewX(0deg); + text-shadow: none; + } +} + +@keyframes glitch-shard-a { + 0% { + opacity: 0.9; + clip-path: polygon(0 0, 62% 0, 54% 34%, 0 34%); + transform: translate(-8px, -4px); + animation-timing-function: steps(1, end); + } + 8% { + clip-path: polygon(6% 4%, 68% 0, 58% 38%, 0 30%); + transform: translate(8px, 4px); + animation-timing-function: steps(1, end); + } + 16% { + opacity: 0.35; + clip-path: polygon(0 0, 50% 6%, 44% 30%, 0 38%); + transform: translate(-3px, -2px); + animation-timing-function: ease-out; + } + 35% { + opacity: 0; + clip-path: polygon(0 0, 62% 0, 54% 34%, 0 34%); + transform: translate(-5px, -3px); + } + 100% { + opacity: 0; + clip-path: polygon(0 0, 62% 0, 54% 34%, 0 34%); + transform: translate(-5px, -3px); + } +} + +@keyframes glitch-shard-b { + 0% { + opacity: 0.9; + clip-path: polygon(38% 66%, 100% 58%, 100% 100%, 30% 100%); + transform: translate(8px, 4px); + animation-timing-function: steps(1, end); + } + 8% { + clip-path: polygon(32% 62%, 100% 66%, 100% 100%, 24% 96%); + transform: translate(-8px, -4px); + animation-timing-function: steps(1, end); + } + 16% { + opacity: 0.35; + clip-path: polygon(50% 70%, 100% 62%, 100% 100%, 44% 100%); + transform: translate(3px, 2px); + animation-timing-function: ease-out; + } + 35% { + opacity: 0; + clip-path: polygon(38% 66%, 100% 58%, 100% 100%, 30% 100%); + transform: translate(5px, 3px); + } + 100% { + opacity: 0; + clip-path: polygon(38% 66%, 100% 58%, 100% 100%, 30% 100%); + transform: translate(5px, 3px); + } +} + +@keyframes glitch-shard-c { + 0% { + opacity: 0.95; + clip-path: polygon(8% 42%, 96% 38%, 88% 62%, 0 66%); + transform: translate(6px, -4px); + animation-timing-function: steps(1, end); + } + 6% { + transform: translate(-6px, 4px); + animation-timing-function: steps(1, end); + } + 12% { + clip-path: polygon(0 46%, 88% 40%, 96% 58%, 4% 62%); + transform: translate(4px, 2px); + animation-timing-function: steps(1, end); + } + 20% { + transform: translate(-2px, -1px); + animation-timing-function: ease-out; + } + 35% { + opacity: 0; + clip-path: polygon(8% 42%, 96% 38%, 88% 62%, 0 66%); + transform: translate(1px, 2px); + } + 100% { + opacity: 0; + clip-path: polygon(8% 42%, 96% 38%, 88% 62%, 0 66%); + transform: translate(1px, 2px); + } +} + +.group:hover .glitch-soft { + animation: glitch-soft 1s ease-out 1; +} + +.group:hover .glitch-soft-late { + animation-delay: 0.1s; +} + +@keyframes glitch-soft { + 0% { + transform: translate(1px, -1px); + text-shadow: + 1px 0 rgba(34, 211, 238, 0.45), + -1px 0 rgba(244, 63, 94, 0.45); + animation-timing-function: steps(1, end); + } + 8% { + transform: translate(-1px, 1px); + text-shadow: + -1px 0 rgba(34, 211, 238, 0.45), + 1px 0 rgba(244, 63, 94, 0.45); + animation-timing-function: steps(1, end); + } + 16% { + transform: translate(1px, 1px); + animation-timing-function: ease-out; + } + 30% { + transform: translate(0, 0); + text-shadow: none; + } + 100% { + transform: translate(0, 0); + text-shadow: none; + } +} + +.glitch-ghost-img { + opacity: 0; +} + +.group:hover .glitch-ghost-img-a { + animation: glitch-img-band-a 2s ease-out 1; +} + +.group:hover .glitch-ghost-img-b { + animation: glitch-img-band-b 2s ease-out 1; +} + +@keyframes glitch-img-band-a { + 0% { + opacity: 0.45; + clip-path: inset(0 0 55% 0); + transform: translate(-4px, 0); + animation-timing-function: steps(1, end); + } + 6% { + clip-path: inset(5% 0 60% 0); + transform: translate(4px, 2px); + animation-timing-function: steps(1, end); + } + 12% { + opacity: 0.35; + clip-path: inset(10% 0 62% 0); + transform: translate(-2px, 0); + } + 100% { + opacity: 0; + clip-path: inset(0 0 55% 0); + transform: translate(0, 0); + } +} + +@keyframes glitch-img-band-b { + 0% { + opacity: 0.45; + clip-path: inset(45% 0 0 0); + transform: translate(4px, 0); + animation-timing-function: steps(1, end); + } + 6% { + clip-path: inset(40% 0 5% 0); + transform: translate(-4px, -2px); + animation-timing-function: steps(1, end); + } + 12% { + opacity: 0.35; + clip-path: inset(38% 0 10% 0); + transform: translate(2px, 0); + } + 100% { + opacity: 0; + clip-path: inset(45% 0 0 0); + transform: translate(0, 0); + } +} + +@media (prefers-reduced-motion: reduce) { + .group:hover .glitch-hover { + animation: none; + } + + .group:hover .glitch-ghost { + animation: none; + opacity: 0; + } + + .group:hover .glitch-soft { + animation: none; + } + + .group:hover .glitch-ghost-img { + animation: none; + opacity: 0; + } +}