He estado estudiando propiedades avanzadas de CSS para lograr visualizaciones más interesantes. Para practicar construí un web-component que anima una carta con perspectiva 3d y recibe un parámetro opcional para elegir su temática

<card-showcase></card-showcase>

La animación la logré con CSS puro, los estilos se componen de 3 partes: La escena, la carta y las caras de la carta

En la escena definimos la posición para dibujar la carta; Su perspectiva y tamaño

.scene {
    transform-style: preserve-3d;
    perspective: 1000px;
    height: 100%;
    width: 100%;
}

.scene .card {
    width: 70%;
    translate: 22% 0 -200px;
}

En la carta definimos la forma del objeto e indicamos que posicionaremos sus elementos hijos con 3d. También indicamos cómo utilizará la carta la animación de rotación

.card {
    aspect-ratio: 2 / 3;
    transform-style: preserve-3d;

    animation-name: showcasing;
    animation-duration: 10s;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}

@keyframes showcasing {
    from {
        transform: rotateY(0);
    }

    to {
        transform: rotateY(-360deg);
    }
}

Como el elemento padre preservará 3d, podemos posicionar las caras de la carta una delante de la otra y rotar 180 grados la cara trasera

.card .face {
    position: absolute;
    border-radius: 10px;
    transition: all 0.3s;
    background-size: 100% 100%;
    width: 100%;
    height: 100%;
}

.card .face.front {
    background-color: lightgray;
}

.card .face.back {
    background-color: darkgray;
    transform: rotateY(180deg) translateZ(0.1px);
}

Para dibujar las imágenes utilizamos el atributo theme del custom element

<card-showcase theme="magic"></card-showcase>

El componente mapea el atributo theme a su propiedad homónima y aplica dinámicamente un estilo background-image en cada cara de la carta, interpolando el valor para armar la url de la imagen

class CardShowcaseComponent extends LitElement {
    ...
    static properties = {
        theme: { type: String, attribute: true },
    }

    render() {
        return html`
            <link rel="stylesheet" href="/assets/css/components/card-showcase.css">
            <div class="scene">
                <div class="card" part="card">
                    <div class="face front" style="background-image: url('/assets/images/components/card-showcase/${this.theme}-front.png');"></div>
                    <div class="face back"  style="background-image: url('/assets/images/components/card-showcase/${this.theme}-back.png');"></div>
                </div>
            </div>
        `;
    }
    ...
}

Si no se le asignó ningún valor, el componente iterará automáticamente entre temáticas cada 20 segundos

class CardShowcaseComponent extends LitElement {
    ...
    #themes = ['magic', 'marvel', 'yugioh'];

    firstUpdated() {
        if (this.theme === undefined) {
            this.setTheme(this.#themes[0]);
            setInterval(() => this.setTheme(), 20000);
        }
    }

    setTheme(newTheme = null) {
        if (newTheme) {
            this.theme = newTheme;
            return;
        }

        const currentThemeIndex = this.#themes.indexOf(this.theme);
        const nextThemeIndex = (currentThemeIndex + 1) % this.#themes.length;

        this.theme = this.#themes[nextThemeIndex];
    }
    ...
}

Una de las ventajas más interesantes de utilizar web-components es que los métodos de su clase están expuestos como propiedades del elemento. Por ejemplo podemos llamar setTheme() desde el exterior para cambiar programáticamente la temática de la carta

const themeChanger = document.querySelector('#theme-changer');
const themeChangerTarget = document.querySelector('#theme-changer-target');

themeChanger.addEventListener('click', () => {
    themeChangerTarget.setTheme();
});