More Cards Snippets
3D Pin Card — Free HTML CSS JS Perspective Hover Snippet
3D Pin Card · Cards · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
3D Pin Card — Perspective Tilt with a Rising Anchor Pin

The 3D pin card is the standout hover interaction from modern component libraries: a card that tilts back in three dimensions when you hover it, while a floating "pin" — a label, a beam, and expanding rings — rises above it as if pinning the card to a point in space. This snippet recreates the whole illusion with plain HTML, CSS 3D transforms, and a little vanilla JavaScript for cursor tracking.
Establishing 3D space
The effect lives inside a perspective: 1000px stage, which is what gives child transforms real depth — without it, rotateX would look like a flat squash. The pin wrapper uses transform-style: preserve-3d so its children (the card, the label, the beam, the rings) each keep their own position in 3D rather than being flattened into the wrapper's plane. This is the foundational setup for any layered 3D effect.
The card's backward tilt
On hover, the inner card rotates with rotateX(28deg), tipping its top edge away from you so you appear to look down at it on a surface. Because the perspective is set on the parent, this reads as the card laying back into the scene. The transition makes it ease in and out smoothly. A radial gradient overlay in the top corner adds a subtle light source consistent with the tilt.
The pin: label, beam, and rings
What makes this more than a tilt is the pin that appears to hold the card. Three elements animate in on hover: a rounded .pc-label pill that floats up and forward via translateZ(60px) so it hovers above the card in 3D; a thin .pc-beam that grows in height with rotateX(70deg) so it lies along the depth axis like a pin's shaft; and two .pc-ring circles, also rotated 70° into the floor plane, that pcPing outward and fade on a staggered loop, like ripples around the pin's base. Together they create the impression of a physical pin staking the card to the ground.
Cursor-tracking tilt
On top of the base rotateX, JavaScript adds pointer-aware lean. A pointermove handler converts the cursor's position within the card into a -0.5…0.5 range and applies rotateY and rotateX to the wrapper, so the whole assembly — card and pin — leans toward the cursor. On pointerleave it resets to flat. Because the wrapper has preserve-3d, the pin elements track the lean correctly, keeping the illusion coherent from any angle.
A real link affordance
The component is an <a>, and the label reads like a URL, so it behaves like a clickable, pinned link. Clicking copies the label text via the navigator.clipboard API and briefly swaps the label to "Copied ✓" — demonstrating how the pin can double as a shareable handle. Swap this for real navigation by removing the preventDefault.
Customizing it
Tune the rotateX(28deg) for a steeper or gentler tilt, change the translateZ to float the label higher, adjust the ring pcPing size and timing, and set the label text to your link. Recolor the beam, rings, and gradient to your accent. Pair it with a focus cards grid or a meteor card for a set of eye-catching 3D cards.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA card renders flat with a hidden pin label above it.
- 2Hover the cardIt tilts back in 3D while a pin label, beam, and rings rise.
- 3Move the cursorThe whole assembly leans toward the pointer.
- 4Watch the ringsTwo circles ping outward around the pin base on a loop.
- 5Click the cardThe label URL copies and shows a Copied confirmation.
- 6Set your linkChange the label text and remove preventDefault to navigate.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
perspective: 1000px on the stage is what gives 3D transforms real depth — it defines how strongly things recede. Without it, rotateX(28deg) just looks like a vertical squash. The pin wrapper also uses transform-style: preserve-3d so the card, label, beam, and rings each keep their own 3D position instead of flattening into one plane.
Three elements animate in on hover: a label pill pushed forward with translateZ(60px) so it hovers above the card, a thin beam rotated 70 degrees into the depth axis that grows in height like a pin shaft, and two rings also rotated into the floor plane that expand and fade on a staggered ping loop, reading as ripples around the pin's base.
A pointermove handler converts the cursor's position inside the card to a -0.5 to 0.5 range and applies rotateY and rotateX to the wrapper, so the whole assembly leans toward the pointer on top of the base tilt. pointerleave resets it to flat. Because the wrapper is preserve-3d, the pin elements lean coherently with the card.
The card is an anchor with a URL-style label. The click handler prevents navigation, copies the label text via navigator.clipboard, and briefly swaps it to Copied so it acts as a shareable handle. To use it as a normal link instead, remove the preventDefault and set a real href.
Render the structure as a component and keep the perspective and preserve-3d CSS. Handle pointermove and pointerleave with framework events, writing the transform via a ref or state-driven inline style. Use the clipboard API in the click handler. The hover-driven pin animations are pure CSS and need no JS. In Tailwind, use perspective and transform utilities with arbitrary translate-z and rotate-x values.
3D Pin Card — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3D Pin Card</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh}
.pc-stage{perspective:1000px;padding:40px}
.pc-pin{position:relative;display:block;width:260px;text-decoration:none;color:inherit;transform-style:preserve-3d;transition:transform .3s ease}
.pc-card{position:relative;border-radius:18px;padding:24px;background:#101019;border:1px solid #23233a;transform:translateZ(0);transition:transform .3s ease;overflow:hidden}
.pc-pin:hover .pc-card{transform:rotateX(28deg) translateZ(0)}
.pc-card h3{font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1}
.pc-card p{font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1}
.pc-grad{position:absolute;inset:0;background:radial-gradient(circle at 30% 0,rgba(129,140,248,.35),transparent 60%)}
/* The pin label, beam, and rings appear above the tilting card on hover */
.pc-label{position:absolute;top:-46px;left:50%;transform:translateX(-50%) translateZ(0);opacity:0;font-size:11px;font-weight:700;letter-spacing:.03em;background:#1e1b4b;border:1px solid #4f46e5;color:#c7d2fe;padding:5px 12px;border-radius:999px;white-space:nowrap;transition:opacity .3s,transform .3s;pointer-events:none}
.pc-pin:hover .pc-label{opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)}
.pc-beam{position:absolute;top:-22px;left:50%;width:1px;height:0;background:linear-gradient(180deg,#818cf8,transparent);transform:translateX(-50%) rotateX(70deg);transform-origin:top;opacity:0;transition:height .35s ease,opacity .3s;pointer-events:none}
.pc-pin:hover .pc-beam{height:90px;opacity:1}
.pc-ring{position:absolute;top:64px;left:50%;width:8px;height:8px;border-radius:50%;border:1px solid rgba(129,140,248,.6);transform:translate(-50%,-50%) rotateX(70deg);opacity:0;pointer-events:none}
.pc-pin:hover .pc-ring{animation:pcPing 2.4s ease-out infinite}
.pc-pin:hover .pc-ring2{animation-delay:1.2s}
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
</style>
</head>
<body>
<div class="pc-stage">
<a href="#" class="pc-pin" id="pcPin">
<span class="pc-label" id="pcLabel">/aceternity.dev</span>
<span class="pc-beam" aria-hidden="true"></span>
<span class="pc-ring" aria-hidden="true"></span>
<span class="pc-ring pc-ring2" aria-hidden="true"></span>
<div class="pc-card">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div class="pc-grad"></div>
</div>
</a>
</div>
<script>
var pin = document.getElementById('pcPin');
var label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3D Pin Card</title>
<!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh
}
.pc-pin:hover .pc-card {
transform:rotateX(28deg) translateZ(0)
}
.pc-card h3 {
font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1
}
.pc-card p {
font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1
}
.pc-pin:hover .pc-label {
opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)
}
.pc-pin:hover .pc-beam {
height:90px;opacity:1
}
.pc-pin:hover .pc-ring {
animation:pcPing 2.4s ease-out infinite
}
.pc-pin:hover .pc-ring2 {
animation-delay:1.2s
}
</style>
</head>
<body>
<div class="[perspective:1000px] p-10">
<a href="#" class="pc-pin relative block w-[260px] no-underline text-inherit [transform-style:preserve-3d] [transition:transform_.3s_ease]" id="pcPin">
<span class="pc-label absolute top-[-46px] left-1/2 [transform:translateX(-50%)_translateZ(0)] opacity-0 text-[11px] font-bold tracking-[.03em] bg-[#1e1b4b] border border-[#4f46e5] text-[#c7d2fe] py-[5px] px-3 rounded-[999px] whitespace-nowrap [transition:opacity_.3s,transform_.3s] pointer-events-none" id="pcLabel">/aceternity.dev</span>
<span class="pc-beam absolute top-[-22px] left-1/2 w-px h-0 [background:linear-gradient(180deg,#818cf8,transparent)] [transform:translateX(-50%)_rotateX(70deg)] [transform-origin:top] opacity-0 [transition:height_.35s_ease,opacity_.3s] pointer-events-none" aria-hidden="true"></span>
<span class="pc-ring absolute top-16 left-1/2 w-2 h-2 rounded-full border border-[rgba(129,140,248,.6)] [transform:translate(-50%,-50%)_rotateX(70deg)] opacity-0 pointer-events-none" aria-hidden="true"></span>
<span class="pc-ring absolute top-16 left-1/2 w-2 h-2 rounded-full border border-[rgba(129,140,248,.6)] [transform:translate(-50%,-50%)_rotateX(70deg)] opacity-0 pointer-events-none pc-ring2" aria-hidden="true"></span>
<div class="pc-card relative rounded-[18px] p-6 bg-[#101019] border border-[#23233a] [transform:translateZ(0)] [transition:transform_.3s_ease] overflow-hidden">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div class="absolute inset-0 [background:radial-gradient(circle_at_30%_0,rgba(129,140,248,.35),transparent_60%)]"></div>
</div>
</a>
</div>
<script>
var pin = document.getElementById('pcPin');
var label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to Snippet3DPinCard.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh}
.pc-stage{perspective:1000px;padding:40px}
.pc-pin{position:relative;display:block;width:260px;text-decoration:none;color:inherit;transform-style:preserve-3d;transition:transform .3s ease}
.pc-card{position:relative;border-radius:18px;padding:24px;background:#101019;border:1px solid #23233a;transform:translateZ(0);transition:transform .3s ease;overflow:hidden}
.pc-pin:hover .pc-card{transform:rotateX(28deg) translateZ(0)}
.pc-card h3{font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1}
.pc-card p{font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1}
.pc-grad{position:absolute;inset:0;background:radial-gradient(circle at 30% 0,rgba(129,140,248,.35),transparent 60%)}
/* The pin label, beam, and rings appear above the tilting card on hover */
.pc-label{position:absolute;top:-46px;left:50%;transform:translateX(-50%) translateZ(0);opacity:0;font-size:11px;font-weight:700;letter-spacing:.03em;background:#1e1b4b;border:1px solid #4f46e5;color:#c7d2fe;padding:5px 12px;border-radius:999px;white-space:nowrap;transition:opacity .3s,transform .3s;pointer-events:none}
.pc-pin:hover .pc-label{opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)}
.pc-beam{position:absolute;top:-22px;left:50%;width:1px;height:0;background:linear-gradient(180deg,#818cf8,transparent);transform:translateX(-50%) rotateX(70deg);transform-origin:top;opacity:0;transition:height .35s ease,opacity .3s;pointer-events:none}
.pc-pin:hover .pc-beam{height:90px;opacity:1}
.pc-ring{position:absolute;top:64px;left:50%;width:8px;height:8px;border-radius:50%;border:1px solid rgba(129,140,248,.6);transform:translate(-50%,-50%) rotateX(70deg);opacity:0;pointer-events:none}
.pc-pin:hover .pc-ring{animation:pcPing 2.4s ease-out infinite}
.pc-pin:hover .pc-ring2{animation-delay:1.2s}
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
`;
export default function Snippet3DPinCard() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var pin = document.getElementById('pcPin');
var label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{css}</style>
<div className="pc-stage">
<a href="#" className="pc-pin" id="pcPin">
<span className="pc-label" id="pcLabel">/aceternity.dev</span>
<span className="pc-beam" aria-hidden="true"></span>
<span className="pc-ring" aria-hidden="true"></span>
<span className="pc-ring pc-ring2" aria-hidden="true"></span>
<div className="pc-card">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div className="pc-grad"></div>
</div>
</a>
</div>
</>
);
}import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+
export default function Snippet3DPinCard() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var pin = document.getElementById('pcPin');
var label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
}, []);
return (
<>
<style>{`
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh
}
.pc-pin:hover .pc-card {
transform:rotateX(28deg) translateZ(0)
}
.pc-card h3 {
font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1
}
.pc-card p {
font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1
}
.pc-pin:hover .pc-label {
opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)
}
.pc-pin:hover .pc-beam {
height:90px;opacity:1
}
.pc-pin:hover .pc-ring {
animation:pcPing 2.4s ease-out infinite
}
.pc-pin:hover .pc-ring2 {
animation-delay:1.2s
}
`}</style>
<div className="[perspective:1000px] p-10">
<a href="#" className="pc-pin relative block w-[260px] no-underline text-inherit [transform-style:preserve-3d] [transition:transform_.3s_ease]" id="pcPin">
<span className="pc-label absolute top-[-46px] left-1/2 [transform:translateX(-50%)_translateZ(0)] opacity-0 text-[11px] font-bold tracking-[.03em] bg-[#1e1b4b] border border-[#4f46e5] text-[#c7d2fe] py-[5px] px-3 rounded-[999px] whitespace-nowrap [transition:opacity_.3s,transform_.3s] pointer-events-none" id="pcLabel">/aceternity.dev</span>
<span className="pc-beam absolute top-[-22px] left-1/2 w-px h-0 [background:linear-gradient(180deg,#818cf8,transparent)] [transform:translateX(-50%)_rotateX(70deg)] [transform-origin:top] opacity-0 [transition:height_.35s_ease,opacity_.3s] pointer-events-none" aria-hidden="true"></span>
<span className="pc-ring absolute top-16 left-1/2 w-2 h-2 rounded-full border border-[rgba(129,140,248,.6)] [transform:translate(-50%,-50%)_rotateX(70deg)] opacity-0 pointer-events-none" aria-hidden="true"></span>
<span className="pc-ring absolute top-16 left-1/2 w-2 h-2 rounded-full border border-[rgba(129,140,248,.6)] [transform:translate(-50%,-50%)_rotateX(70deg)] opacity-0 pointer-events-none pc-ring2" aria-hidden="true"></span>
<div className="pc-card relative rounded-[18px] p-6 bg-[#101019] border border-[#23233a] [transform:translateZ(0)] [transition:transform_.3s_ease] overflow-hidden">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div className="absolute inset-0 [background:radial-gradient(circle_at_30%_0,rgba(129,140,248,.35),transparent_60%)]"></div>
</div>
</a>
</div>
</>
);
}<template>
<div class="pc-stage">
<a href="#" class="pc-pin" id="pcPin">
<span class="pc-label" id="pcLabel">/aceternity.dev</span>
<span class="pc-beam" aria-hidden="true"></span>
<span class="pc-ring" aria-hidden="true"></span>
<span class="pc-ring pc-ring2" aria-hidden="true"></span>
<div class="pc-card">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div class="pc-grad"></div>
</div>
</a>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
let pin;
let label;
onMounted(() => {
pin = document.getElementById('pcPin');
label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
});
</script>
<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh}
.pc-stage{perspective:1000px;padding:40px}
.pc-pin{position:relative;display:block;width:260px;text-decoration:none;color:inherit;transform-style:preserve-3d;transition:transform .3s ease}
.pc-card{position:relative;border-radius:18px;padding:24px;background:#101019;border:1px solid #23233a;transform:translateZ(0);transition:transform .3s ease;overflow:hidden}
.pc-pin:hover .pc-card{transform:rotateX(28deg) translateZ(0)}
.pc-card h3{font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1}
.pc-card p{font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1}
.pc-grad{position:absolute;inset:0;background:radial-gradient(circle at 30% 0,rgba(129,140,248,.35),transparent 60%)}
/* The pin label, beam, and rings appear above the tilting card on hover */
.pc-label{position:absolute;top:-46px;left:50%;transform:translateX(-50%) translateZ(0);opacity:0;font-size:11px;font-weight:700;letter-spacing:.03em;background:#1e1b4b;border:1px solid #4f46e5;color:#c7d2fe;padding:5px 12px;border-radius:999px;white-space:nowrap;transition:opacity .3s,transform .3s;pointer-events:none}
.pc-pin:hover .pc-label{opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)}
.pc-beam{position:absolute;top:-22px;left:50%;width:1px;height:0;background:linear-gradient(180deg,#818cf8,transparent);transform:translateX(-50%) rotateX(70deg);transform-origin:top;opacity:0;transition:height .35s ease,opacity .3s;pointer-events:none}
.pc-pin:hover .pc-beam{height:90px;opacity:1}
.pc-ring{position:absolute;top:64px;left:50%;width:8px;height:8px;border-radius:50%;border:1px solid rgba(129,140,248,.6);transform:translate(-50%,-50%) rotateX(70deg);opacity:0;pointer-events:none}
.pc-pin:hover .pc-ring{animation:pcPing 2.4s ease-out infinite}
.pc-pin:hover .pc-ring2{animation-delay:1.2s}
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
</style>// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-snippet3-d-pin-card',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="pc-stage">
<a href="#" class="pc-pin" id="pcPin">
<span class="pc-label" id="pcLabel">/aceternity.dev</span>
<span class="pc-beam" aria-hidden="true"></span>
<span class="pc-ring" aria-hidden="true"></span>
<span class="pc-ring pc-ring2" aria-hidden="true"></span>
<div class="pc-card">
<h3>Build the future</h3>
<p>Hover this card — it tilts in 3D and a perspective pin rises to anchor it in space.</p>
<div class="pc-grad"></div>
</div>
</a>
</div>
`,
styles: [`
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#06060c;color:#fff;display:flex;justify-content:center;align-items:center;min-height:100vh}
.pc-stage{perspective:1000px;padding:40px}
.pc-pin{position:relative;display:block;width:260px;text-decoration:none;color:inherit;transform-style:preserve-3d;transition:transform .3s ease}
.pc-card{position:relative;border-radius:18px;padding:24px;background:#101019;border:1px solid #23233a;transform:translateZ(0);transition:transform .3s ease;overflow:hidden}
.pc-pin:hover .pc-card{transform:rotateX(28deg) translateZ(0)}
.pc-card h3{font-size:20px;font-weight:800;margin-bottom:8px;position:relative;z-index:1}
.pc-card p{font-size:13.5px;color:#a3a3bd;line-height:1.55;position:relative;z-index:1}
.pc-grad{position:absolute;inset:0;background:radial-gradient(circle at 30% 0,rgba(129,140,248,.35),transparent 60%)}
/* The pin label, beam, and rings appear above the tilting card on hover */
.pc-label{position:absolute;top:-46px;left:50%;transform:translateX(-50%) translateZ(0);opacity:0;font-size:11px;font-weight:700;letter-spacing:.03em;background:#1e1b4b;border:1px solid #4f46e5;color:#c7d2fe;padding:5px 12px;border-radius:999px;white-space:nowrap;transition:opacity .3s,transform .3s;pointer-events:none}
.pc-pin:hover .pc-label{opacity:1;transform:translateX(-50%) translateY(-6px) translateZ(60px)}
.pc-beam{position:absolute;top:-22px;left:50%;width:1px;height:0;background:linear-gradient(180deg,#818cf8,transparent);transform:translateX(-50%) rotateX(70deg);transform-origin:top;opacity:0;transition:height .35s ease,opacity .3s;pointer-events:none}
.pc-pin:hover .pc-beam{height:90px;opacity:1}
.pc-ring{position:absolute;top:64px;left:50%;width:8px;height:8px;border-radius:50%;border:1px solid rgba(129,140,248,.6);transform:translate(-50%,-50%) rotateX(70deg);opacity:0;pointer-events:none}
.pc-pin:hover .pc-ring{animation:pcPing 2.4s ease-out infinite}
.pc-pin:hover .pc-ring2{animation-delay:1.2s}
@keyframes pcPing{0%{opacity:.7;width:8px;height:8px}100%{opacity:0;width:90px;height:90px}}
`]
})
export class Snippet3DPinCardComponent implements AfterViewInit {
ngAfterViewInit(): void {
var pin = document.getElementById('pcPin');
var label = document.getElementById('pcLabel');
// Pointer-aware tilt: the card leans toward the cursor while hovered, layered
// on top of the base rotateX so the 3D pin illusion tracks the mouse.
pin.addEventListener('pointermove', function (e) {
var r = pin.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width - 0.5; // -0.5 .. 0.5
var py = (e.clientY - r.top) / r.height - 0.5;
pin.style.transform = 'rotateY(' + (px * 14) + 'deg) rotateX(' + (-py * 10) + 'deg)';
});
pin.addEventListener('pointerleave', function () {
pin.style.transform = 'rotateY(0) rotateX(0)';
});
// Clicking copies the label "url" to show the pin as a real link affordance.
pin.addEventListener('click', function (e) {
e.preventDefault();
if (navigator.clipboard) {
navigator.clipboard.writeText(label.textContent.trim()).then(function () {
var prev = label.textContent;
label.textContent = 'Copied ✓';
setTimeout(function () { label.textContent = prev; }, 1100);
});
}
});
}
}