Index Next: Mask Layer - Pokemon Cards CSS Previous: Pokemon Cards CSS

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: grain

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