Skip to content

Instantly share code, notes, and snippets.

@HoughIO
Last active March 11, 2026 06:41
Show Gist options
  • Select an option

  • Save HoughIO/3ea501592a59a5504bdd90b8b9075471 to your computer and use it in GitHub Desktop.

Select an option

Save HoughIO/3ea501592a59a5504bdd90b8b9075471 to your computer and use it in GitHub Desktop.
Trading cards with back button
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pet Trading Cards</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
overflow-x: hidden;
}
.header {
text-align: center;
padding: 2rem 1rem;
color: white;
}
.header h1 {
font-size: 2.5rem;
background: linear-gradient(90deg, #ffd700, #ff8c00, #ff6b6b, #ff8c00, #ffd700);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: shine 3s linear infinite;
margin-bottom: 0.5rem;
}
.header p {
color: rgba(255,255,255,0.6);
font-size: 1rem;
}
.back-btn {
display: inline-block;
color: #ffd700;
text-decoration: none;
font-size: 0.9rem;
margin-bottom: 1rem;
padding: 0.5rem 1rem;
border: 1px solid rgba(255,215,0,0.3);
border-radius: 2rem;
transition: all 0.3s;
}
.back-btn:hover {
background: rgba(255,215,0,0.1);
border-color: #ffd700;
}
@keyframes shine {
to { background-position: 200% center; }
}
/* Filter buttons */
.filters {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
padding: 0 1rem;
}
.filter-btn {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.2);
color: white;
padding: 0.5rem 1.5rem;
border-radius: 2rem;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
}
.filter-btn:hover, .filter-btn.active {
background: linear-gradient(135deg, #ffd700, #ff8c00);
border-color: transparent;
color: black;
transform: scale(1.05);
}
/* Swiper */
.swiper {
width: 100%;
padding: 2rem 0 4rem;
}
.swiper-slide {
display: flex;
justify-content: center;
align-items: center;
width: 320px;
}
.swiper-pagination-bullet {
background: rgba(255,255,255,0.5);
}
.swiper-pagination-bullet-active {
background: #ffd700;
}
.swiper-button-next, .swiper-button-prev {
color: #ffd700;
}
/* Card Styles */
:root {
--pointer-x: 50%;
--pointer-y: 50%;
--card-opacity: 0;
--rotate-x: 0deg;
--rotate-y: 0deg;
}
.card-container {
perspective: 1500px;
width: 280px;
}
.card {
position: relative;
width: 100%;
aspect-ratio: 0.714;
border-radius: 4.5% / 3.2%;
transform-style: preserve-3d;
transform: rotateY(var(--rotate-x)) rotateX(var(--rotate-y));
transition: transform 0.15s ease-out, box-shadow 0.3s;
cursor: grab;
box-shadow:
0 0 5px -1px rgba(255,255,255,0.5),
0 0 10px 1px rgba(255,215,0,0.3),
0 0 20px 3px rgba(255,215,0,0.2),
0px 30px 60px -15px rgba(0,0,0,0.8);
}
.card:hover {
box-shadow:
0 0 10px 0px rgba(255,255,255,0.8),
0 0 20px 3px rgba(255,215,0,0.5),
0 0 40px 8px rgba(255,215,0,0.3),
0px 40px 80px -20px rgba(0,0,0,0.9);
}
.card__image {
position: absolute;
inset: 0;
border-radius: inherit;
overflow: hidden;
background: #1a1a2e;
}
.card__image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top;
}
.card__shine {
position: absolute;
inset: 0;
border-radius: inherit;
background:
repeating-linear-gradient(
82deg,
hsl(53, 65%, 60%) 0%,
hsl(93, 56%, 50%) 8%,
hsl(176, 54%, 49%) 16%,
hsl(228, 59%, 55%) 24%,
hsl(283, 60%, 55%) 32%,
hsl(326, 59%, 51%) 40%,
hsl(326, 59%, 51%) 48%,
hsl(283, 60%, 55%) 56%,
hsl(228, 59%, 55%) 64%,
hsl(176, 54%, 49%) 72%,
hsl(93, 56%, 50%) 80%,
hsl(53, 65%, 60%) 88%
);
background-size: 400% 900%;
background-position:
calc(10% + (var(--pointer-x) * 0.8))
calc(10% + (var(--pointer-y) * 0.8));
filter: brightness(0.8) contrast(2.5) saturate(0.8);
mix-blend-mode: color-dodge;
opacity: var(--card-opacity);
pointer-events: none;
}
.card__glare {
position: absolute;
inset: 0;
border-radius: inherit;
background: radial-gradient(
farthest-corner circle at var(--pointer-x) var(--pointer-y),
hsla(0, 0%, 100%, 0.8) 10%,
hsla(0, 0%, 100%, 0.4) 30%,
hsla(0, 0%, 0%, 0.5) 90%
);
mix-blend-mode: overlay;
opacity: var(--card-opacity);
pointer-events: none;
}
.card__sparkle {
position: absolute;
inset: 0;
border-radius: inherit;
background-image:
radial-gradient(circle at 20% 30%, rgba(255,255,255,0.9) 0%, transparent 2%),
radial-gradient(circle at 80% 20%, rgba(255,255,255,0.7) 0%, transparent 1.5%),
radial-gradient(circle at 40% 70%, rgba(255,255,255,0.8) 0%, transparent 1.8%),
radial-gradient(circle at 70% 60%, rgba(255,255,255,0.6) 0%, transparent 1.2%),
radial-gradient(circle at 30% 50%, rgba(255,255,255,0.7) 0%, transparent 1.5%),
radial-gradient(circle at 60% 40%, rgba(255,255,255,0.8) 0%, transparent 1.7%),
radial-gradient(circle at 85% 75%, rgba(255,255,255,0.6) 0%, transparent 1.3%),
radial-gradient(circle at 15% 80%, rgba(255,255,255,0.7) 0%, transparent 1.4%);
opacity: calc(var(--card-opacity) * 0.9);
pointer-events: none;
animation: sparkle 2.5s ease-in-out infinite;
}
@keyframes sparkle {
0%, 100% { opacity: calc(var(--card-opacity) * 0.6); }
50% { opacity: var(--card-opacity); }
}
.card__label {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.9));
padding: 3rem 1rem 1rem;
color: white;
text-align: center;
border-radius: 0 0 inherit inherit;
}
.card__name {
font-size: 1rem;
font-weight: bold;
background: linear-gradient(90deg, #ffd700, #fff, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.25rem;
}
.card__theme {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 2px;
color: rgba(255,255,255,0.6);
}
/* Mobile touch hint */
.touch-hint {
text-align: center;
color: rgba(255,255,255,0.4);
font-size: 0.8rem;
padding: 1rem;
padding-bottom: 100px;
}
@media (min-width: 768px) {
.touch-hint { display: none; }
}
/* Bottom tabs */
.tabs {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
background: #0a0a0a;
border-top: 1px solid rgba(255,255,255,0.08);
padding-bottom: env(safe-area-inset-bottom);
z-index: 100;
}
.tab {
flex: 1;
padding: 12px;
text-align: center;
color: rgba(255,255,255,0.5);
font-size: 0.75rem;
cursor: pointer;
text-decoration: none;
display: block;
}
.tab.active {
color: #ff6b6b;
}
.tab-icon {
font-size: 1.4rem;
display: block;
margin-bottom: 4px;
}
.swiper {
padding-bottom: 100px;
}
</style>
</head>
<body>
<div class="header">
<a href="index.html" class="back-btn">← Back to Cam</a>
<h1>🎴 Pet Trading Cards 🎴</h1>
<p>Swipe to browse β€’ Hover for holographic effect</p>
</div>
<div class="filters">
<button class="filter-btn active" data-filter="all">All Cards</button>
<button class="filter-btn" data-filter="kevin">🐱 Kevin</button>
<button class="filter-btn" data-filter="todd">🐱 Todd</button>
<button class="filter-btn" data-filter="simba">πŸ• Simba</button>
</div>
<div class="swiper">
<div class="swiper-wrapper" id="cards-wrapper">
<!-- Cards injected by JS -->
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<div class="touch-hint">πŸ‘† Tap and drag cards to feel the holo effect</div>
<!-- Bottom nav to match main app -->
<nav class="tabs">
<a href="index.html" class="tab"><span class="tab-icon">πŸ“Ί</span>Live</a>
<a href="index.html#clips" class="tab"><span class="tab-icon">🎬</span>Clips</a>
<a href="trading-cards.html" class="tab active"><span class="tab-icon">🎴</span>Cards</a>
<a href="index.html#activity" class="tab"><span class="tab-icon">πŸ””</span>Activity</a>
</nav>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script>
const CARDS = [
// Kevin
{ pet: 'kevin', name: 'Kevin', theme: 'Samurai', image: 'trading-cards/batch-001/kevin-samurai.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Space Captain', image: 'trading-cards/batch-001/kevin-space-captain.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Vampire', image: 'trading-cards/batch-001/kevin-vampire.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Chef', image: 'trading-cards/batch-001/kevin-chef.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Disco', image: 'trading-cards/batch-001/kevin-disco.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Sopranos', image: 'trading-cards/batch-001/kevin-sopranos.png' },
{ pet: 'kevin', name: 'Kevin', theme: 'Classical', image: 'trading-cards/batch-001/kevin-classical.png' },
// Todd
{ pet: 'todd', name: 'Todd', theme: 'Samurai', image: 'trading-cards/batch-001/todd-samurai.png' },
{ pet: 'todd', name: 'Todd', theme: 'Space Captain', image: 'trading-cards/batch-001/todd-space-captain.png' },
{ pet: 'todd', name: 'Todd', theme: 'Vampire', image: 'trading-cards/batch-001/todd-vampire.png' },
{ pet: 'todd', name: 'Todd', theme: 'Chef', image: 'trading-cards/batch-001/todd-chef.png' },
{ pet: 'todd', name: 'Todd', theme: 'Disco', image: 'trading-cards/batch-001/todd-disco.png' },
{ pet: 'todd', name: 'Todd', theme: 'Sopranos', image: 'trading-cards/batch-001/todd-sopranos.png' },
{ pet: 'todd', name: 'Todd', theme: 'Classical', image: 'trading-cards/batch-001/todd-classical.png' },
// Simba
{ pet: 'simba', name: 'Simba', theme: 'Samurai', image: 'trading-cards/batch-001/simba-samurai.png' },
{ pet: 'simba', name: 'Simba', theme: 'Space Captain', image: 'trading-cards/batch-001/simba-space-captain.png' },
{ pet: 'simba', name: 'Simba', theme: 'Vampire', image: 'trading-cards/batch-001/simba-vampire.png' },
{ pet: 'simba', name: 'Simba', theme: 'Chef', image: 'trading-cards/batch-001/simba-chef.png' },
{ pet: 'simba', name: 'Simba', theme: 'Disco', image: 'trading-cards/batch-001/simba-disco.png' },
{ pet: 'simba', name: 'Simba', theme: 'Sopranos', image: 'trading-cards/batch-001/simba-sopranos.png' },
{ pet: 'simba', name: 'Simba', theme: 'Classical', image: 'trading-cards/batch-001/simba-classical.png' },
];
let currentFilter = 'all';
let swiper;
function createCardHTML(card) {
return `
<div class="swiper-slide" data-pet="${card.pet}">
<div class="card-container">
<div class="card">
<div class="card__image">
<img src="${card.image}" alt="${card.name} - ${card.theme}" loading="lazy">
</div>
<div class="card__shine"></div>
<div class="card__glare"></div>
<div class="card__sparkle"></div>
</div>
</div>
</div>
`;
}
function renderCards(filter = 'all') {
const wrapper = document.getElementById('cards-wrapper');
const filtered = filter === 'all' ? CARDS : CARDS.filter(c => c.pet === filter);
wrapper.innerHTML = filtered.map(createCardHTML).join('');
if (swiper) {
swiper.destroy();
}
swiper = new Swiper('.swiper', {
effect: 'coverflow',
grabCursor: true,
centeredSlides: true,
slidesPerView: 'auto',
coverflowEffect: {
rotate: 15,
stretch: 0,
depth: 200,
modifier: 1,
slideShadows: true,
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
keyboard: true,
loop: filtered.length > 3,
});
initCardEffects();
}
function initCardEffects() {
document.querySelectorAll('.card').forEach(card => {
const handleMove = (clientX, clientY) => {
const rect = card.getBoundingClientRect();
const x = clientX - rect.left;
const y = clientY - rect.top;
const percentX = (x / rect.width) * 100;
const percentY = (y / rect.height) * 100;
const centerX = percentX - 50;
const centerY = percentY - 50;
card.style.setProperty('--pointer-x', percentX + '%');
card.style.setProperty('--pointer-y', percentY + '%');
card.style.setProperty('--rotate-x', (centerX / 4) + 'deg');
card.style.setProperty('--rotate-y', (-centerY / 4) + 'deg');
card.style.setProperty('--card-opacity', '0.75');
};
const handleEnd = () => {
card.style.setProperty('--rotate-x', '0deg');
card.style.setProperty('--rotate-y', '0deg');
card.style.setProperty('--card-opacity', '0');
};
// Mouse
card.addEventListener('mousemove', e => handleMove(e.clientX, e.clientY));
card.addEventListener('mouseleave', handleEnd);
// Touch
card.addEventListener('touchmove', e => {
if (e.touches.length === 1) {
handleMove(e.touches[0].clientX, e.touches[0].clientY);
}
});
card.addEventListener('touchend', handleEnd);
});
}
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderCards(btn.dataset.filter);
});
});
// Initial render
renderCards();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment