From d400db8969727f73b6e08336650844aa8389f2ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Jan 2026 06:43:48 +0000 Subject: [PATCH 1/4] feat: Add trading card effect to profile picture Add TradingCard component with Pokemon-card-inspired holographic effects: - 3D tilt on mouse hover with perspective transform - Holographic shine overlay with rainbow gradients and blend modes - Animated shimmer border using primary/secondary colors - Glare effect that follows cursor position - Accessibility: respects prefers-reduced-motion Wrap profile picture in About section with new trading card component. --- .../components/molecules/TradingCard.svelte | 243 ++++++++++++++++++ src/lib/components/organisms/About.svelte | 29 ++- 2 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 src/lib/components/molecules/TradingCard.svelte diff --git a/src/lib/components/molecules/TradingCard.svelte b/src/lib/components/molecules/TradingCard.svelte new file mode 100644 index 0000000..538e533 --- /dev/null +++ b/src/lib/components/molecules/TradingCard.svelte @@ -0,0 +1,243 @@ + + + + + diff --git a/src/lib/components/organisms/About.svelte b/src/lib/components/organisms/About.svelte index 288aa13..b8500e6 100644 --- a/src/lib/components/organisms/About.svelte +++ b/src/lib/components/organisms/About.svelte @@ -1,5 +1,6 @@
- +
- -
+ + {#if backgroundSrc} +
+ {/if} + + +
- + +
+ +
- +
{@render children?.()}
@@ -81,18 +117,18 @@ @use '$lib/scss/breakpoints.scss'; .trading-card-container { - --card-border-width: 8px; - --card-radius: 16px; - --shine-intensity: 0.4; - --glare-intensity: 0.15; + --card-border-width: 10px; + --card-radius: 20px; + --glitter-size: 2px; + --glitter-spacing: 24px; - perspective: 1000px; + perspective: 1200px; width: fit-content; } .card-perspective { transform-style: preserve-3d; - transition: transform 0.15s ease-out; + transition: transform 0.1s ease-out; transform: rotateX(var(--rotate-x)) rotateY(var(--rotate-y)); } @@ -100,20 +136,22 @@ position: relative; border-radius: var(--card-radius); transform-style: preserve-3d; - transition: - transform 0.3s ease, - box-shadow 0.3s ease; + transition: box-shadow 0.3s ease; - // Base shadow + // Base shadow with colored glow box-shadow: - 0 10px 30px -10px rgba(0, 0, 0, 0.5), - 0 0 0 1px rgba(var(--color--primary-rgb), 0.1); + 0 15px 35px -10px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(var(--color--primary-rgb), 0.2), + 0 0 20px rgba(var(--color--primary-rgb), 0.1), + 0 0 40px rgba(var(--color--secondary-rgb), 0.05); &.hovering { box-shadow: - 0 20px 50px -15px rgba(0, 0, 0, 0.6), - 0 0 30px rgba(var(--color--primary-rgb), 0.3), - 0 0 60px rgba(var(--color--secondary-rgb), 0.15); + 0 25px 60px -15px rgba(0, 0, 0, 0.6), + 0 0 0 2px rgba(var(--color--primary-rgb), 0.4), + 0 0 40px rgba(var(--color--primary-rgb), 0.4), + 0 0 80px rgba(var(--color--secondary-rgb), 0.2), + 0 0 120px rgba(255, 215, 0, 0.1); } } @@ -123,48 +161,121 @@ border-radius: var(--card-radius); padding: var(--card-border-width); - // Holographic border gradient + // Animated holographic border - VMAX style rainbow background: linear-gradient( - 135deg, - rgba(var(--color--primary-rgb), 0.8) 0%, - rgba(var(--color--secondary-rgb), 0.6) 25%, - rgba(var(--color--primary-rgb), 0.7) 50%, - rgba(var(--color--secondary-rgb), 0.8) 75%, - rgba(var(--color--primary-rgb), 0.6) 100% + var(--border-angle, 135deg), + rgba(var(--color--primary-rgb), 1) 0%, + rgba(255, 215, 0, 0.9) 15%, + rgba(var(--color--secondary-rgb), 1) 30%, + rgba(180, 100, 255, 0.9) 45%, + rgba(var(--color--primary-rgb), 1) 60%, + rgba(255, 215, 0, 0.9) 75%, + rgba(var(--color--secondary-rgb), 1) 90%, + rgba(var(--color--primary-rgb), 1) 100% ); - background-size: 400% 400%; - animation: borderShimmer 8s ease infinite; + background-size: 300% 300%; + animation: borderRainbow 6s linear infinite; } - .card-shine { + // Blurred background image layer + .card-background { position: absolute; - inset: 0; - border-radius: var(--card-radius); + inset: var(--card-border-width); + border-radius: calc(var(--card-radius) - var(--card-border-width)); + z-index: 1; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + inset: -20%; + background-image: var(--bg-image); + background-size: 140%; + background-position: center; + filter: blur(20px) saturate(1.5) brightness(0.7); + } + } + + // Glitter/sparkle effect layer + .card-glitter { + position: absolute; + inset: var(--card-border-width); + border-radius: calc(var(--card-radius) - var(--card-border-width)); + z-index: 2; pointer-events: none; + overflow: hidden; + + // Multi-layer sparkle pattern + background: + // Animated sparkle dots - layer 1 + repeating-radial-gradient( + circle at calc(var(--mouse-x) * 0.5 + 25%) calc(var(--mouse-y) * 0.5 + 25%), + rgba(255, 255, 255, 0.8) 0px, + rgba(255, 255, 255, 0.8) var(--glitter-size), + transparent var(--glitter-size), + transparent var(--glitter-spacing) + ), + // Sparkle dots - layer 2 (offset) + repeating-radial-gradient( + circle at calc(var(--mouse-x) * 0.3 + 50%) calc(var(--mouse-y) * 0.3 + 50%), + rgba(var(--color--secondary-rgb), 0.9) 0px, + rgba(var(--color--secondary-rgb), 0.9) calc(var(--glitter-size) * 0.8), + transparent calc(var(--glitter-size) * 0.8), + transparent calc(var(--glitter-spacing) * 1.3) + ), + // Sparkle dots - layer 3 (gold) + repeating-radial-gradient( + circle at calc(100% - var(--mouse-x) * 0.4) calc(100% - var(--mouse-y) * 0.4), + rgba(255, 215, 0, 0.9) 0px, + rgba(255, 215, 0, 0.9) calc(var(--glitter-size) * 0.6), + transparent calc(var(--glitter-size) * 0.6), + transparent calc(var(--glitter-spacing) * 0.9) + ), + // Sparkle dots - layer 4 (primary color) + repeating-radial-gradient( + circle at calc(var(--mouse-x) * 0.6 + 10%) calc(var(--mouse-y) * 0.6 + 10%), + rgba(var(--color--primary-rgb), 0.85) 0px, + rgba(var(--color--primary-rgb), 0.85) calc(var(--glitter-size) * 0.7), + transparent calc(var(--glitter-size) * 0.7), + transparent calc(var(--glitter-spacing) * 1.1) + ); + + mix-blend-mode: screen; + opacity: 0; + transition: opacity 0.4s ease; + animation: glitterShimmer 3s ease-in-out infinite; + + .hovering & { + opacity: 0.9; + } + } + + // Holographic rainbow effect + .card-holo { + position: absolute; + inset: var(--card-border-width); + border-radius: calc(var(--card-radius) - var(--card-border-width)); z-index: 3; + pointer-events: none; - // Holographic rainbow gradient that follows mouse - background: radial-gradient( - circle at var(--mouse-x) var(--mouse-y), - rgba(255, 255, 255, 0.3) 0%, - transparent 50% - ), - linear-gradient( - 125deg, + // Rainbow gradient that shifts with mouse + background: linear-gradient( + calc(var(--mouse-x) * 3.6deg), transparent 0%, - rgba(var(--color--secondary-rgb), var(--shine-intensity)) 20%, - transparent 30%, - rgba(255, 215, 0, var(--shine-intensity)) 45%, - transparent 55%, - rgba(var(--color--primary-rgb), var(--shine-intensity)) 70%, - transparent 80%, - rgba(var(--color--secondary-rgb), var(--shine-intensity)) 95%, - transparent 100% + rgba(255, 0, 128, 0.15) 10%, + rgba(var(--color--secondary-rgb), 0.2) 20%, + rgba(0, 255, 128, 0.15) 30%, + transparent 40%, + rgba(255, 215, 0, 0.15) 50%, + transparent 60%, + rgba(var(--color--primary-rgb), 0.2) 70%, + rgba(180, 100, 255, 0.15) 80%, + transparent 90%, + rgba(var(--color--secondary-rgb), 0.15) 100% ); - background-size: 100% 100%, 300% 300%; - background-position: - 0% 0%, - calc(var(--mouse-x) * 1%) calc(var(--mouse-y) * 1%); + background-size: 200% 200%; + background-position: calc(var(--mouse-x) * 1%) calc(var(--mouse-y) * 1%); + mix-blend-mode: color-dodge; opacity: 0; transition: opacity 0.3s ease; @@ -174,19 +285,29 @@ } } + // Specular glare highlight .card-glare { position: absolute; - inset: 0; - border-radius: var(--card-radius); - pointer-events: none; + inset: var(--card-border-width); + border-radius: calc(var(--card-radius) - var(--card-border-width)); z-index: 4; + pointer-events: none; + + background: + // Main glare spot + radial-gradient( + ellipse 60% 40% at var(--mouse-x) var(--mouse-y), + rgba(255, 255, 255, 0.35) 0%, + rgba(255, 255, 255, 0.1) 30%, + transparent 70% + ), + // Secondary softer glow + radial-gradient( + ellipse 100% 80% at var(--mouse-x) var(--mouse-y), + rgba(255, 255, 255, 0.1) 0%, + transparent 50% + ); - // Specular highlight that follows cursor - background: radial-gradient( - ellipse 80% 50% at var(--mouse-x) var(--mouse-y), - rgba(255, 255, 255, var(--glare-intensity)) 0%, - transparent 60% - ); opacity: 0; transition: opacity 0.3s ease; @@ -195,33 +316,56 @@ } } + // Foreground content .card-content { position: relative; - z-index: 2; - border-radius: calc(var(--card-radius) - var(--card-border-width) / 2); + z-index: 5; + border-radius: calc(var(--card-radius) - var(--card-border-width)); overflow: hidden; - background: var(--color--card-background); - - // Inner shadow for depth - box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3); + background: transparent; :global(img), :global(picture) { display: block; width: 100%; height: auto; + // Slight mask to blend edges with glitter background + mask-image: radial-gradient( + ellipse 95% 95% at center, + black 70%, + rgba(0, 0, 0, 0.95) 85%, + rgba(0, 0, 0, 0.8) 100% + ); + -webkit-mask-image: radial-gradient( + ellipse 95% 95% at center, + black 70%, + rgba(0, 0, 0, 0.95) 85%, + rgba(0, 0, 0, 0.8) 100% + ); } } - @keyframes borderShimmer { + @keyframes borderRainbow { 0% { background-position: 0% 50%; + --border-angle: 0deg; } 50% { background-position: 100% 50%; + --border-angle: 180deg; } 100% { background-position: 0% 50%; + --border-angle: 360deg; + } + } + + @keyframes glitterShimmer { + 0%, 100% { + filter: brightness(1) contrast(1); + } + 50% { + filter: brightness(1.2) contrast(1.1); } } @@ -229,14 +373,20 @@ @media (prefers-reduced-motion: reduce) { .card-frame { animation: none; + background-size: 100% 100%; } .card-perspective { transform: none !important; } - .card-shine, - .card-glare { + .card-glitter { + animation: none; + } + + .card-holo, + .card-glare, + .card-glitter { display: none; } } diff --git a/src/lib/components/organisms/About.svelte b/src/lib/components/organisms/About.svelte index b8500e6..9e4571b 100644 --- a/src/lib/components/organisms/About.svelte +++ b/src/lib/components/organisms/About.svelte @@ -38,7 +38,7 @@
- + Date: Wed, 14 Jan 2026 18:43:43 +0000 Subject: [PATCH 3/4] fix: Make glitter effect visible by default, enhance on hover - Increase base opacity for glitter (0.6), holo (0.5), glare (0.3) - Add holoShift animation for continuous rainbow movement - Enhance sparkle brightness in glitterShimmer animation - Effects now always visible and enhanced on hover - Update reduced-motion to show static effects instead of hiding --- .../components/molecules/TradingCard.svelte | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/lib/components/molecules/TradingCard.svelte b/src/lib/components/molecules/TradingCard.svelte index e4d43ce..ea00a74 100644 --- a/src/lib/components/molecules/TradingCard.svelte +++ b/src/lib/components/molecules/TradingCard.svelte @@ -210,43 +210,43 @@ // Animated sparkle dots - layer 1 repeating-radial-gradient( circle at calc(var(--mouse-x) * 0.5 + 25%) calc(var(--mouse-y) * 0.5 + 25%), - rgba(255, 255, 255, 0.8) 0px, - rgba(255, 255, 255, 0.8) var(--glitter-size), + rgba(255, 255, 255, 0.9) 0px, + rgba(255, 255, 255, 0.9) var(--glitter-size), transparent var(--glitter-size), transparent var(--glitter-spacing) ), // Sparkle dots - layer 2 (offset) repeating-radial-gradient( circle at calc(var(--mouse-x) * 0.3 + 50%) calc(var(--mouse-y) * 0.3 + 50%), - rgba(var(--color--secondary-rgb), 0.9) 0px, - rgba(var(--color--secondary-rgb), 0.9) calc(var(--glitter-size) * 0.8), + rgba(var(--color--secondary-rgb), 1) 0px, + rgba(var(--color--secondary-rgb), 1) calc(var(--glitter-size) * 0.8), transparent calc(var(--glitter-size) * 0.8), transparent calc(var(--glitter-spacing) * 1.3) ), // Sparkle dots - layer 3 (gold) repeating-radial-gradient( circle at calc(100% - var(--mouse-x) * 0.4) calc(100% - var(--mouse-y) * 0.4), - rgba(255, 215, 0, 0.9) 0px, - rgba(255, 215, 0, 0.9) calc(var(--glitter-size) * 0.6), + rgba(255, 215, 0, 1) 0px, + rgba(255, 215, 0, 1) calc(var(--glitter-size) * 0.6), transparent calc(var(--glitter-size) * 0.6), transparent calc(var(--glitter-spacing) * 0.9) ), // Sparkle dots - layer 4 (primary color) repeating-radial-gradient( circle at calc(var(--mouse-x) * 0.6 + 10%) calc(var(--mouse-y) * 0.6 + 10%), - rgba(var(--color--primary-rgb), 0.85) 0px, - rgba(var(--color--primary-rgb), 0.85) calc(var(--glitter-size) * 0.7), + rgba(var(--color--primary-rgb), 1) 0px, + rgba(var(--color--primary-rgb), 1) calc(var(--glitter-size) * 0.7), transparent calc(var(--glitter-size) * 0.7), transparent calc(var(--glitter-spacing) * 1.1) ); mix-blend-mode: screen; - opacity: 0; + opacity: 0.6; transition: opacity 0.4s ease; animation: glitterShimmer 3s ease-in-out infinite; .hovering & { - opacity: 0.9; + opacity: 1; } } @@ -262,23 +262,24 @@ background: linear-gradient( calc(var(--mouse-x) * 3.6deg), transparent 0%, - rgba(255, 0, 128, 0.15) 10%, - rgba(var(--color--secondary-rgb), 0.2) 20%, - rgba(0, 255, 128, 0.15) 30%, + rgba(255, 0, 128, 0.2) 10%, + rgba(var(--color--secondary-rgb), 0.25) 20%, + rgba(0, 255, 128, 0.2) 30%, transparent 40%, - rgba(255, 215, 0, 0.15) 50%, + rgba(255, 215, 0, 0.2) 50%, transparent 60%, - rgba(var(--color--primary-rgb), 0.2) 70%, - rgba(180, 100, 255, 0.15) 80%, + rgba(var(--color--primary-rgb), 0.25) 70%, + rgba(180, 100, 255, 0.2) 80%, transparent 90%, - rgba(var(--color--secondary-rgb), 0.15) 100% + rgba(var(--color--secondary-rgb), 0.2) 100% ); background-size: 200% 200%; background-position: calc(var(--mouse-x) * 1%) calc(var(--mouse-y) * 1%); mix-blend-mode: color-dodge; - opacity: 0; + opacity: 0.5; transition: opacity 0.3s ease; + animation: holoShift 8s ease-in-out infinite; .hovering & { opacity: 1; @@ -297,18 +298,18 @@ // Main glare spot radial-gradient( ellipse 60% 40% at var(--mouse-x) var(--mouse-y), - rgba(255, 255, 255, 0.35) 0%, - rgba(255, 255, 255, 0.1) 30%, + rgba(255, 255, 255, 0.4) 0%, + rgba(255, 255, 255, 0.15) 30%, transparent 70% ), // Secondary softer glow radial-gradient( ellipse 100% 80% at var(--mouse-x) var(--mouse-y), - rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.15) 0%, transparent 50% ); - opacity: 0; + opacity: 0.3; transition: opacity 0.3s ease; .hovering & { @@ -365,7 +366,22 @@ filter: brightness(1) contrast(1); } 50% { - filter: brightness(1.2) contrast(1.1); + filter: brightness(1.3) contrast(1.15); + } + } + + @keyframes holoShift { + 0%, 100% { + background-position: 0% 50%; + } + 25% { + background-position: 100% 0%; + } + 50% { + background-position: 100% 100%; + } + 75% { + background-position: 0% 100%; } } @@ -380,14 +396,15 @@ transform: none !important; } - .card-glitter { + .card-glitter, + .card-holo { animation: none; } .card-holo, .card-glare, .card-glitter { - display: none; + opacity: 0.3; } } From cd0dbf21787a703b4de5300ca2a8542ba4acd7cc Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Jan 2026 18:54:07 +0000 Subject: [PATCH 4/4] fix: Fix glitter effect z-index - effects now overlay on top of image Complete restructure of layer ordering: - Image content: z-index 1 (bottom) - Glitter sparkles: z-index 10 (overlay on image) - Holographic rainbow: z-index 11 - Specular glare: z-index 12 (topmost) Sparkle improvements: - Use individual radial-gradient dots for visible sparkles - 20 sparkle points in white, cyan, gold, orange colors - mix-blend-mode: overlay (hard-light on hover) - sparkleFloat animation for pulsing brightness Effects are now properly visible on top of the profile image. --- .../components/molecules/TradingCard.svelte | 291 ++++++------------ 1 file changed, 100 insertions(+), 191 deletions(-) diff --git a/src/lib/components/molecules/TradingCard.svelte b/src/lib/components/molecules/TradingCard.svelte index ea00a74..92a2cd6 100644 --- a/src/lib/components/molecules/TradingCard.svelte +++ b/src/lib/components/molecules/TradingCard.svelte @@ -20,14 +20,10 @@ let rotateX = $derived(isHovering ? (mouseY - 50) / 4 : 0); let rotateY = $derived(isHovering ? (50 - mouseX) / 4 : 0); - // Shine/glitter position follows mouse with offset for parallax + // Shine/glitter position follows mouse let shineX = $derived(mouseX); let shineY = $derived(mouseY); - // Seed for sparkle variation - let seedX = $derived(Math.floor(mouseX / 5) * 5); - let seedY = $derived(Math.floor(mouseY / 5) * 5); - function handleMouseMove(event: MouseEvent) { if (!cardElement) return; @@ -52,17 +48,14 @@ // Get the best quality image URL from the Picture object function getImageUrl(src: Picture | undefined): string { if (!src) return ''; - // Get the first source's largest image if (src.sources && src.sources.length > 0) { const srcset = src.sources[0].srcset; - // Extract the largest image URL from srcset const parts = srcset.split(','); if (parts.length > 0) { const lastPart = parts[parts.length - 1].trim(); return lastPart.split(' ')[0]; } } - // Fallback to img.src return src.img?.src || ''; } @@ -81,46 +74,34 @@ --mouse-y: {shineY}%; --rotate-x: {rotateX}deg; --rotate-y: {rotateY}deg; - --seed-x: {seedX}; - --seed-y: {seedY}; --bg-image: url('{bgImageUrl}'); " >
-
- - {#if backgroundSrc} -
- {/if} + +
+ {@render children?.()} +
- +
- +
- +
- - -
- {@render children?.()} -