From 50881b8534a9bcfe46536b76f2f6dee92fcd4ee8 Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Sat, 29 Aug 2026 16:25:19 -0600 Subject: [PATCH] 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 --- app/globals.css | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/app/globals.css b/app/globals.css index d469446..33a4cc9 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1710,6 +1710,133 @@ html.theme-vaporwave .vw-palm--right-small { 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. */ html.theme-vaporwave .bg-primary { box-shadow: 0 4px 18px oklch(0.62 0.25 350 / 0.35);