CartesGratuit

Holo Tilt & Glare

Une carte de 250 × 156 px inclinée jusqu'à ±11° / ±9° par le pointeur, un glare conique irisé centré sous le curseur et une couche de micro-stries qui glisse à l'inverse — six variables CSS écrites à chaque frame par une boucle rAF lissée (12 % du chemin par image), qui s'arrête au repos. Sans JS, la carte est statique.

JSPointer3DHolographic
GOLD MEMBER A. Dubois MBR‑2026‑0042
Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Cartes en contient 41 au total, dont 6 gratuits. Effect.Labs regroupe 811 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Votre carte membre Présentez-la en caisse pour cumuler des points.
4 820pointsOrniveau12/2026renouvellement

Déplacez la souris sur la carte

① Carte membre — espace client ou programme de fidélité

QuandPage de compte : un titre, la carte au centre, points, niveau et date de renouvellement en dessous.
PourquoiLe tilt et le glare imitent une carte physique premium que l'on fait jouer dans la lumière ; le format 250 × 156 est celui d'une carte bancaire (ratio 1,6) et le texte en bas à gauche, celui des vraies cartes. Une carte par page, le survol suffit.
RéglagesCode vendu, role="img" à la place du tabindex, dégradé sombre sous le contenu et fond derrière le badge (numéro 7,2:1, badge ≥ 7,2:1), badge 11 px.
Saison classée 12 · Carte de joueur
68 % victoires214 parties3.1 K/D

Déplacez la souris sur la carte

② Carte de joueur — profil, rang et saison classée

QuandProfil d'un jeu ou d'une plateforme e-sport : la carte du joueur, ses statistiques en pastilles.
PourquoiLe glare irisé est le code visuel des cartes à collectionner « holo », les stries celui de la foil imprimée : le joueur reconnaît l'objet. Le retour élastique au repos donne le poids d'une carte réelle.
RéglagesFond conique recoloré en émeraude et sarcelle (#0b2a24, #12463d, #0b2a3d, #1c3a2a), badge #5eead4, amplitude portée à ±14° (tx = (px − .5) × 28) et lissage .16 pour une carte plus vive.
Vous êtes sur la liste Votre pass est personnel — présentez-le à l'entrée backstage. Ouverture des portes à 21 h

Déplacez la souris sur la carte

③ Invitation VIP — pass d'un événement

QuandPage de confirmation d'un événement : le pass personnel à présenter, l'heure d'ouverture des portes.
PourquoiUne carte-objet vaut mieux qu'un QR code brut pour dire « vous êtes attendu » : l'inclinaison au survol donne envie de la toucher, et le contenu reste du texte sélectionnable.
RéglagesFond conique chaud (#2a1606, #4a2a10, #1c1440, #3a2410), badge doré #ffd98a, glare adouci (opacity: .5; filter: blur(8px)), nom en serif, position de repos déplacée à 30 % / 80 %.

Comment ça marche

Trois calques dans .htg-card (250 × 156 px, border-radius: 14px; overflow: hidden, ombre 0 18px 40px rgba(0,0,0,.5)), elle-même dans .htg-wrap qui porte perspective: 900px. Le fond de la carte est un radial-gradient blanc à 16 % centré sur (--htg-mx, --htg-my) posé sur un conic-gradient à cinq arrêts (from 230deg at 40% 30% : #1c1440 → #232e63 → #14324d → #3a1c55 → #1c1440). Son transform est rotateX(var(--htg-ry)) rotateY(var(--htg-rx)) — les noms sont croisés (rx alimente rotateY), le rendu est juste, la lecture du code moins.

Le glare : .htg-glare (inset: 0) est un conic-gradient(from 0deg at var(--htg-mx) var(--htg-my), …) à six arrêts — cyan .5, rose .45, jaune .4, violet .45, vert .4, cyan .5 — centré sous le pointeur, en mix-blend-mode: screen (il ne peut qu'éclaircir), opacité .62, filter: blur(6px). La foil : .htg-foil déborde de 30 % (inset: -30%) et répète un repeating-linear-gradient(104deg) de période 7 px (1 px blanc à 9 %, 2 px vides, 1 px blanc à 4,3 %, 3 px vides) en color-dodge ; elle est translatée de (--htg-fx, --htg-fy) = −(mx − 50) × 0,5 px, −(my − 50) × 0,4 px, soit jusqu'à ±25 / ±20 px à l'opposé du pointeur — la parallaxe inverse qui fait lire les stries comme une couche sous la surface.

Le script, par .htg-wrap : pointermove normalise la position du pointeur dans le rectangle de la carte (px, py de 0 à 1) et fixe des cibles — tx = (px − .5) × 22rotateY ±11°, ty = −(py − .5) × 18rotateX ±9°, tmx, tmy = px, py × 100 pour le centre du glare. Une boucle requestAnimationFrame avance chaque valeur de 12 % du chemin restant par image (95 % en ≈ 23 images, ≈ 390 ms à 60 Hz ; mesuré 325 ms pour revenir au repos), écrit les six variables (toFixed) et s'arrête dès que les rotations sont à moins de .05° et mx à moins de .2 % de leur cible — au repos, rien ne tourne. pointerleave renvoie les cibles à 0°, 0°, 70 %, 25 % : les valeurs par défaut du CSS, la carte se repose exactement là où elle était.

Deux choses que le code veut faire et ne fait pas. .htg-content porte transform: translateZ(24px) et la carte transform-style: preserve-3d pour détacher le texte de la surface — mais overflow: hidden sur la même carte force la valeur utilisée à flat : le contenu n'est pas soulevé. Mesuré dans Chromium, carte inclinée : le rectangle du nom est identique avec et sans translateZ ; il se décale de 6 px dès que l'on passe overflow: visible. Et le test d'arrêt de la boucle ignore --htg-my : l'amortissement étant uniforme, la valeur se pose à ~0,3 % près, sans conséquence visible. Sous prefers-reduced-motion: reduce, le script sort avant d'attacher le moindre écouteur : carte fixe, glare à 70 % / 25 %.

Accessibilité

  • prefers-reduced-motion géré dans le JS : aucun écouteur n'est posé, la carte reste dans son état de repos avec sa matière holographique (glare fixe à 70 % / 25 %) — le repli est visuellement complet.
  • .htg-wrap porte tabindex="0" et un aria-label mais aucun role : c'est un arrêt de tabulation qui ne réagit à aucune touche (le script n'écoute que le pointeur), et un aria-label sur une div sans rôle est ignoré par plusieurs lecteurs d'écran. Soit retirez tabindex (élément décoratif), soit ajoutez role="img" avec un libellé qui décrit la carte (« Carte membre, niveau Or, A. Dubois ») — et traduisez-le, le libellé vendu est en français.
  • Contraste mesuré (Chromium, texte rendu transparent, pixels sous le texte, glare compris, trois positions de pointeur) : nom #fff 16,8 px gras ≥ 4,7:1 partout — texte large, seuil 3:1, conforme ; numéro rgba(255,255,255,.75) 11,2 px : 3,4 à 4,1:1 au minimum, sous 4,5:1 ; badge #7dffc4 9,3 px gras : 3,3 à 4,1:1 au minimum (médiane 4,1 à 5,2) dans le passage cyan-vert du glare, sous 4,5:1. Correction calculée sur le pixel le plus clair mesuré : un dégradé sombre sous le contenu, .htg-content { background: linear-gradient(transparent 40%, rgba(10,8,30,.55)) } → numéro 7,2:1 (11,3:1 en #fff) ; et .htg-badge { background: rgba(10,8,30,.6) } → badge 7,2 à 9,7:1.
  • Tailles : badge .58rem = 9,3 px, numéro .7rem = 11,2 px — sous toute taille de lecture confortable ; l'ombre text-shadow: 0 1px 3px rgba(0,0,0,.65) ne compte pas pour WCAG (le minimum mesuré ne change pas avec elle). Passez le badge à 11 px au moins.
  • Tactile : les Pointer Events couvrent le doigt — glisser sur la carte l'incline, le défilement n'est pas bloqué, pointerleave se déclenche au relâchement et la carte revient. will-change: transform sur un élément de 250 × 156 px est peu coûteux. Aucun contenu n'est généré : le nom accessible dépend du seul libellé.

Compatibilité navigateur

Requiert conic-gradient(), mix-blend-mode (screen, color-dodge), les transformations 3D avec perspective, les propriétés personnalisées écrites par setProperty et les Pointer Events. Aucune bibliothèque.

Chrome 69+✓ Complet
Firefox 83+✓ Complet
Safari 13+✓ Complet
Edge 79+✓ Complet
Mobile iOS 13+✓ Complet (au doigt)
Android Chrome✓ Complet (au doigt)

Sans JavaScript, la carte est statique : glare à 70 % / 25 %, foil non décalée — la matière holographique reste, seul le mouvement manque. Sans conic-gradient (navigateurs d'avant 2019-2020), la déclaration background entière est invalide et la carte perd son fond : ajoutez background-color: #1c1440 en repli.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="htg-wrap" tabindex="0" aria-label="Carte holographique pilotée au pointeur">
  <div class="htg-card">
    <span class="htg-glare" aria-hidden="true">
    </span>
    <span class="htg-foil" aria-hidden="true">
    </span>
    <span class="htg-content">
      <span class="htg-badge">GOLD MEMBER</span>
      <strong class="htg-name">A. Dubois</strong>
      <span class="htg-num">MBR‑2026‑0042</span>
    </span>
  </div>
</div>
CSS
.htg-wrap  {
   perspective: 900px;
   display: inline-block;
   border-radius: 16px;
   }

.htg-wrap:focus-visible  {
   outline: rgb(168, 85, 247) solid 2px;
   outline-offset: 6px;
   }

.htg-card  {
   position: relative;
   width: 250px;
   height: 156px;
   border-radius: 14px;
   overflow: hidden;
   background: radial-gradient(120% 160% at var(--htg-mx, 70%) var(--htg-my, 20%), rgba(255,255,255,.16), transparent 55%),
                conic-gradient(from 230deg at 40% 30%, #1c1440, #232e63, #14324d, #3a1c55, #1c1440);
   transform: rotateX(var(--htg-ry, 0deg)) rotateY(var(--htg-rx, 0deg));
   box-shadow: rgba(0, 0, 0, 0.5) 0px 18px 40px;
   transform-style: preserve-3d;
   will-change: transform;
   }

.htg-glare  {
   position: absolute;
   inset: 0px;
   pointer-events: none;
   background: conic-gradient(from 0deg at var(--htg-mx, 70%) var(--htg-my, 25%),
                rgba(98,230,255,.5), rgba(255,138,225,.45), rgba(255,233,92,.4), rgba(177,140,255,.45), rgba(125,255,196,.4), rgba(98,230,255,.5));
   mix-blend-mode: screen;
   opacity: 0.62;
   filter: blur(6px);
   }

.htg-foil  {
   position: absolute;
   inset: -30%;
   pointer-events: none;
   background: repeating-linear-gradient(104deg, rgba(255, 255, 255, 0.09) 0px, rgba(255, 255, 255, 0.09) 1px, transparent 1px, transparent 3px, rgba(255, 255, 255, 0.043) 3px, rgba(255, 255, 255, 0.043) 4px, transparent 4px, transparent 7px);
   mix-blend-mode: color-dodge;
   transform: translate(var(--htg-fx, 0px), var(--htg-fy, 0px));
   }

.htg-content  {
   position: absolute;
   inset: 0px;
   padding: 16px 18px;
   display: flex;
   flex-direction: column;
   justify-content: flex-end;
   gap: 3px;
   transform: translateZ(24px);
   text-shadow: rgba(0, 0, 0, 0.65) 0px 1px 3px;
   }

.htg-badge  {
   align-self: flex-start;
   font-size: 0.58rem;
   font-weight: 800;
   letter-spacing: 0.16em;
   color: rgb(125, 255, 196);
   border: 1px solid rgba(125, 255, 196, 0.45);
   border-radius: 20px;
   padding: 3px 9px;
   margin-bottom: auto;
   }

.htg-name  {
   font-size: 1.05rem;
   color: rgb(255, 255, 255);
   }

.htg-num  {
   font-size: 0.7rem;
   letter-spacing: 0.18em;
   color: rgba(255, 255, 255, 0.75);
   }
JavaScript (fx-0821)
(function() {
  /* fx-0821 — Holo Tilt & Glare : tilt + glare + foil pilotés au pointeur (rAF + lerp) */
  function initHtg() {
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    document.querySelectorAll('.htg-wrap').forEach(function(wrap) {
      var card = wrap.querySelector('.htg-card');
      if (!card) return;
      var tx = 0, ty = 0, tmx = 70, tmy = 25, x = 0, y = 0, mx = 70, my = 25, raf = null;
      function loop() {
        x += (tx - x) * 0.12; y += (ty - y) * 0.12; mx += (tmx - mx) * 0.12; my += (tmy - my) * 0.12;
        card.style.setProperty('--htg-rx', x.toFixed(2) + 'deg');
        card.style.setProperty('--htg-ry', y.toFixed(2) + 'deg');
        card.style.setProperty('--htg-mx', mx.toFixed(1) + '%');
        card.style.setProperty('--htg-my', my.toFixed(1) + '%');
        card.style.setProperty('--htg-fx', (-(mx - 50) * 0.5).toFixed(1) + 'px');
        card.style.setProperty('--htg-fy', (-(my - 50) * 0.4).toFixed(1) + 'px');
        if (Math.abs(tx - x) > 0.05 || Math.abs(ty - y) > 0.05 || Math.abs(tmx - mx) > 0.2) raf = requestAnimationFrame(loop);
        else raf = null;
      }
      function kick() { if (!raf) raf = requestAnimationFrame(loop); }
      wrap.addEventListener('pointermove', function(e) {
        var r = card.getBoundingClientRect();
        var px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
        tx = (px - 0.5) * 22; ty = -(py - 0.5) * 18; tmx = px * 100; tmy = py * 100; kick();
      });
      wrap.addEventListener('pointerleave', function() { tx = 0; ty = 0; tmx = 70; tmy = 25; kick(); });
    });
  }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initHtg);
  else initHtg();
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Amplitude (JS — tx = (px − .5) * 22, ty = −(py − .5) * 18) ±11° / ±9° Degrés d'inclinaison horizontale et verticale. 30 / 24 = carte très mobile ; 12 / 10 = discret. Au-delà de 30°, les bords révèlent l'absence d'épaisseur.
Lissage (JS — * 0.12, quatre occurrences) 0,12 Fraction du chemin restant parcourue par image. .25 = réactif (~180 ms) ; .06 = lourd (~800 ms). La valeur dépend du taux de rafraîchissement : à 120 Hz, deux fois plus vite.
Glare (CSS — .htg-glare : six arrêts, opacity .62, blur 6px) cyan, rose, jaune, violet, vert Les couleurs du reflet en mode screen. Opacité .4 pour une carte sobre ; supprimez le blur pour des rayons nets ; deux couleurs suffisent pour un reflet bicolore.
Foil (CSS — .htg-foil repeating-linear-gradient 104deg, période 7 px) 1 px blanc 9 % / 1 px blanc 4,3 % Les micro-stries. Espacez (transparent 1px 5px) pour un grain plus visible ; changez l'angle (60deg) pour croiser la lumière du glare.
Parallaxe inverse (JS — −(mx − 50) * 0.5, −(my − 50) * 0.4) ±25 / ±20 px Course de la foil. Le signe négatif fait la profondeur ; en positif la couche paraît collée dessus. Restez sous la marge de 30 % (inset −30 %), sinon la foil découvre les bords.
Repos (JS — pointerleave 70 / 25 ; CSS — var(--htg-mx, 70%) var(--htg-my, 25%)) 70 % / 25 % Position du reflet au repos. Les valeurs JS et les défauts CSS doivent rester identiques, sinon la carte saute au premier survol puis au retour.
Fond (CSS — .htg-card conic-gradient from 230deg) #1c1440 → #232e63 → #14324d → #3a1c55 La matière de base. Gardez des tons sombres : le glare éclaircit (screen) et le texte blanc a besoin de fond. Ajoutez background-color en repli pour les navigateurs sans conic-gradient.

FAQ

Le texte n'a aucun relief quand la carte s'incline — le translateZ(24px) ne sert à rien ?

Dans le code livré, non. overflow: hidden sur .htg-card aplatit le contexte 3D (transform-style utilisé = flat), et un translateZ dans un contexte aplati n'a aucun effet visible — mesuré : le nom ne bouge pas d'un pixel. Pour retrouver le relief, déplacez le rognage : enveloppez .htg-glare et .htg-foil dans un span à inset: 0; border-radius: 14px; overflow: hidden, et retirez overflow: hidden de la carte. Le texte se détache alors de 24 px et se déplace légèrement à contresens de la rotation.

Plusieurs cartes sur la même page ?

Oui : le script fait querySelectorAll('.htg-wrap') et donne à chaque carte son propre état et sa propre boucle, qui ne tourne que pendant qu'elle bouge. Coût par carte en mouvement : six setProperty par image et un getBoundingClientRect par pointermove. Aucun id, aucune variable globale.

Comment garder l'inclinaison sans le reflet irisé — ou l'inverse ?

Sans glare : supprimez le span.htg-glare (le script continue d'écrire --htg-mx/my, qui ne servent plus qu'au halo blanc du fond). Sans inclinaison : mettez tx = 0; ty = 0 dans pointermove ou remplacez la ligne transform de la carte par none — le reflet et la foil continuent de suivre le pointeur, la carte reste de face.