Shine Layers of Pokemon cards implemented in CSS and javascript
In the previous post I explained how to create a basic 3D card in JS/CSS. This post is about the "shine" layers used to create the holographic effect.
There is one main element in the HTML with class .card__shine and then a
pseudo-element is created: ::after. Some cards also have a ::before layer
too.
The HTML is similar to the previous post but now there's the <div class="card__shine"> element:
<div class="card" id="holocard">
<div class="card__rotator">
<div class="card__front">
<!-- <img src="" width="330" height="460" /> -->
<div class="card__shine"></div>
</div>
</div>
</div>
Let's look at each CSS layer one at a time. The cards all differ in technique so each one needs to be looked at individually but the one I used for this website has two identical layers that slide in opposite directions as the mouse moves.
The basic background image is some texture grain.webp:

Then a rainbow layer is blended onto it:
.card__shine {
background-image:
url("/pokemon-cards-css/img/grain.webp"),
repeating-linear-gradient(
0deg,
rgb(255, 122, 117) 5%,
rgb(255, 237, 97) 10%,
rgb(168, 255, 97) 15%,
rgb(133, 255, 247) 20%,
rgb(122, 149, 255) 25%,
rgb(216, 117, 255) 30%,
rgb(255, 122, 117) 35%);
background-blend-mode: screen, hue, hard-light;
background-size: 500px 100%, 200% 700%;
background-position: center, 0% var(--background-y);
filter: brightness(.8) contrast(2.95) saturate(.65);
}
Note that there is a --background-y variable now. This is controlled by
javascript and makes the rainbow slide around with the mouse pointer. We'll set
--background-x while we're here because that's used soon. Actually --pointer-x
and --pointer-y will be used later as well. They are the same as the background
variables but I think Simon wanted to keep them semantically different in the
code.
// These lines are added to the interact function that we previously defined
card.style.setProperty('--pointer-x', `${pointerXPc}%`);
card.style.setProperty('--pointer-y', `${pointerYPc}%`);
card.style.setProperty('--background-x', `${pointerXPc}%`);
card.style.setProperty('--background-y', `${pointerYPc}%`);
Next we blend in another layer that also moves with the mouse pointer and creates diagonal bands of light:
.card__shine {
background-image:
...stuff from before...
repeating-linear-gradient(
133deg,
rgb(14, 21, 46) 0%,
rgb(143, 163, 163) 3.8%,
rgb(143, 193, 193) 4.5%,
rgb(143, 163, 163) 5.2%,
rgb(14, 21, 46) 10%,
rgb(14, 21, 46) 12%);
background-blend-mode: screen, hue, hard-light;
background-size: 500px 100%, 200% 700%, 300% 100%;
background-position: center, 0% var(--background-y), var(--background-x) var(--background-y);
filter: brightness(.8) contrast(2.95) saturate(.65);
}
Yet another layer is blended in, a radial-gradiant that provides a bit of glare but I couldn't tell the difference. Later on we'll add more glare but I think this was supposed to be like the light reflecting off the foil at the back of the card.
.card__shine {
background-image:
... previous stuff ...
radial-gradient(
farthest-corner circle
at var(--pointer-x) var(--pointer-y),
rgba(0, 0, 0, 0.1) 12%,
rgba(0, 0, 0, 0.15) 20%,
rgba(0, 0, 0, 0.25) 120%);
}
background-size: 500px 100%, 200% 700%, 300% 100%, 200% 100%;
background-position: center,
0% var(--background-y),
var(--background-x) var(--background-y),
var(--background-x) var(--background-y);
Now we get to the pseudo element ::after. It shares the same css as the
.card__shine layer but overrides the background-position and background-size
properties so that it moves in the opposite direction. The filter and blend
modes are different too so that they interact - sometimes cancelling and
sometimes boosting the glow. I don't know why, but it's important to set
content: ""; and display: grid;, otherwise the ::after layer doesn't show.
.card__shine::after {
content: "";
display: grid;
background-position:
center,
0% var(--background-y),
calc( var(--background-x) * -1) calc( var(--background-y) * -1),
var(--background-x) var(--background-y);
background-size: 500px 100%, 200% 400%, 195% 100%, 200% 100%;
filter: brightness(1) contrast(2.5) saturate(1.75);
mix-blend-mode: soft-light;
}
If it looks blank, then hover the mouse over it. My browser shows an empty box until the interact function is called. Maybe it's a z-index issue.
Next: Mask Layer of Pokemon cards implemented in CSS and javascript