Add VHS glitch animation to vaporwave theme cards

- Periodic jitter, red/cyan title split, and a tracking band sweep on
  each card for a few hundred ms every cycle
- Stagger three phases (10s/13s/17s) with offsets so the board flickers
  organically instead of in lockstep
- Gate with :not(.opacity-50) so dnd-kit's inline transform isn't overridden
  by the animation while a card is being dragged
This commit is contained in:
Brian Fertig 2026-08-29 16:25:19 -06:00
parent 7fcfcbdb5b
commit 50881b8534
1 changed files with 127 additions and 0 deletions

View File

@ -1710,6 +1710,133 @@ html.theme-vaporwave .vw-palm--right-small {
transform: scaleX(-1); transform: scaleX(-1);
} }
/* VHS hiccup: every so often a card drops out of the signal for half a
second -- a 1-2px jitter, a red/cyan split on the title, and a tracking
band sweeping down the panel, all inside one short window (the first 5%
of each cycle). Cards run three alternating phases with different periods
and offsets, so the board flickers organically instead of in lockstep.
The :not(.opacity-50) guard matters: while dnd-kit drives a drag it sets
an inline `transform` on the card, and a running animation would override
that inline value (animations sit above inline styles in the cascade) --
the card stops following the pointer. opacity-50 is exactly the class the
card gets while dragging, so the hiccup stands down for the duration.
(overflow: hidden keeps the tracking band clipped to the card while it
sweeps; the hover lift uses the separate `translate` property, so the
jitter doesn't fight it.) */
html.theme-vaporwave [data-group-card]:not(.opacity-50) {
overflow: hidden;
animation: vw-card-glitch 10s linear infinite;
}
html.theme-vaporwave [data-group-card]:not(.opacity-50) > div:first-child h3 {
animation: vw-title-glitch 10s linear infinite;
}
html.theme-vaporwave [data-group-card]:not(.opacity-50)::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: -12%;
height: 12px;
opacity: 0;
pointer-events: none;
background:
repeating-linear-gradient(0deg, oklch(0.45 0.15 330 / 0.28) 0 1px, transparent 1px 3px),
linear-gradient(
180deg,
transparent 0%,
oklch(0.72 0.2 350 / 0.22) 20%,
oklch(0.99 0.01 220 / 0.4) 50%,
oklch(0.78 0.15 200 / 0.22) 80%,
transparent 100%
);
animation: vw-tracking 10s linear infinite;
}
/* Phase B: a longer period and an offset start, so neighbors don't hiccup
together. (Static styles above are the idle state -- before a card's
first delay elapses, and under reduced motion, everything just sits still.) */
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50),
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50) > div:first-child h3,
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50)::after {
animation-duration: 13s;
animation-delay: 3s;
}
/* Phase C: the longest period, offset furthest. */
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50),
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50) > div:first-child h3,
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50)::after {
animation-duration: 17s;
animation-delay: 7s;
}
@keyframes vw-card-glitch {
0%,
5%,
100% {
transform: translateX(0);
}
0.8% {
transform: translateX(-1.5px);
}
1.6% {
transform: translateX(-1.5px);
}
2.4% {
transform: translateX(1px);
}
3.2% {
transform: translateX(1px);
}
4% {
transform: translateX(-0.75px);
}
4.6% {
transform: translateX(0.5px);
}
}
@keyframes vw-title-glitch {
0%,
5%,
100% {
text-shadow: none;
}
0.8% {
text-shadow:
1.5px 0 oklch(0.68 0.2 350 / 0.55),
-1.5px 0 oklch(0.85 0.15 200 / 0.55);
}
1.6% {
text-shadow:
-1.5px 0 oklch(0.68 0.2 350 / 0.55),
1.5px 0 oklch(0.85 0.15 200 / 0.55);
}
2.4% {
text-shadow:
1px 0 oklch(0.68 0.2 350 / 0.55),
-1px 0 oklch(0.85 0.15 200 / 0.55);
}
3.2% {
text-shadow:
-0.5px 0 oklch(0.68 0.2 350 / 0.55),
0.5px 0 oklch(0.85 0.15 200 / 0.55);
}
}
@keyframes vw-tracking {
0% {
top: -12%;
opacity: 0;
}
0.8% {
opacity: 0.85;
}
4.8% {
opacity: 0.85;
}
5%,
100% {
top: 112%;
opacity: 0;
}
}
/* Primary blooms with a soft magenta glow; focus glows electric cyan. */ /* Primary blooms with a soft magenta glow; focus glows electric cyan. */
html.theme-vaporwave .bg-primary { html.theme-vaporwave .bg-primary {
box-shadow: 0 4px 18px oklch(0.62 0.25 350 / 0.35); box-shadow: 0 4px 18px oklch(0.62 0.25 350 / 0.35);