:root {
--main-padding-block: 10rem;
--main-padding-inline: 2rem;
--accent: #785589;
}
.lab-grid {
--lab-grid__width: 1080px;
--lab-grid__gap: 2em;
--lab-intro__heading__font-size: 1.25em;
--lab-intro__max-width: 36em;
--lab-intro__margin-bottom: 2em;
display: grid;
gap: var(--lab-grid__gap);
max-width: var(--lab-grid__width);
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.lab-intro {
font-size: var(--lab-intro__heading__font-size);
max-width: var(--lab-intro__max-width);
margin-bottom: var(--lab-intro__margin-bottom);
}
.card-label {
margin-top: 0.6rem;
font-size: 0.75rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #6b7280;
text-align: center;
}
/* ============================================
BASE — shared defaults + mechanics
Every --hc-* var below is a default that any
modifier is free to override, in full or part.
============================================ */
.card {
--hc-radius: 0.75rem;
--hc-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 12px rgba(0,0,0,0.2);
--hc-shadow-hover: 0 4px 8px rgba(0,0,0,0.3), 0 24px 48px rgba(0,0,0,0.4);
--hc-lift: -4px;
--hc-card-scale: 1;
--hc-scale: 1;
--hc-rotate: 0deg;
--hc-translate-x: 0;
--hc-translate-y: 0;
--hc-filter: none;
--hc-origin: center;
--hc-perspective: none;
--hc-transition-duration: 450ms;
--hc-transition-function: cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
aspect-ratio: 4 / 3;
touch-action: manipulation;
border-radius: var(--hc-radius);
background: #888;
border: 1px solid hsl(0 0 0 / 5%);
box-shadow: var(--hc-shadow);
perspective: var(--hc-perspective);
transform: scale(var(--hc-card-scale));
transition:
box-shadow var(--hc-transition-duration) var(--hc-transition-function),
transform var(--hc-transition-duration) var(--hc-transition-function);
}
.card:hover,
.card:focus-within,
.card:active {
--hc-card-scale: 1.015;
box-shadow: var(--hc-shadow-hover);
transform: translateY(var(--hc-lift)) scale(var(--hc-card-scale));
}
.card__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transform-origin: var(--hc-origin);
scale: var(--hc-scale);
rotate: var(--hc-rotate);
translate: var(--hc-translate-x) var(--hc-translate-y);
filter: var(--hc-filter);
will-change: transform;
backface-visibility: hidden;
transition:
scale var(--hc-transition-duration) var(--hc-transition-function),
rotate var(--hc-transition-duration) var(--hc-transition-function),
translate var(--hc-transition-duration) var(--hc-transition-function),
filter var(--hc-transition-duration) var(--hc-transition-function);
}
/* ============================================
MODIFIERS
Each one only declares the --hc-* properties
it actually needs — resting state on the base
class, changed state on its own :hover block.
============================================ */
/* 1. Default — plain, quiet baseline */
.card--default {
--hc-transition-duration: 450ms;
}
.card--default:hover,
.card--default:focus-within,
.card--default:active {
--hc-scale: 1.06;
}
/* 2. Snap — near-instant, for dense dashboards / thumbnails */
.card--snap {
--hc-origin: 0 0;
--hc-transition-duration: 160ms;
--hc-transition-function: cubic-bezier(0.22, 1, 0.36, 1);
}
.card--snap:hover,
.card--snap:focus-within,
.card--snap:active {
--hc-scale: 1.08;
}
/* 3. Tilt — soft 3D pickup, like lifting a photo off a table */
.card--tilt {
--hc-scale: 1.04;
--hc-perspective: 900px;
--hc-transition-duration: 500ms;
--hc-transition-function: cubic-bezier(0.22, 1, 0.36, 1);
}
.card--tilt:hover,
.card--tilt:focus-within,
.card--tilt:active {
--hc-scale: 1.1;
--hc-rotate: 0 1 0.15 6deg;
}
/* 4. Rotate Pop — playful spring, slight overshoot */
.card--rotate {
--hc-transition-duration: 550ms;
--hc-transition-function: cubic-bezier(0.34, 1.56, 0.64, 1);
}
.card--rotate:hover,
.card--rotate:focus-within,
.card--rotate:active {
--hc-scale: 1.08;
--hc-rotate: 2.5deg;
}
/* 5. Cinematic — slow, patient, luxurious */
.card--cinematic {
--hc-origin: 50% 60%;
--hc-transition-duration: 1100ms;
--hc-transition-function: cubic-bezier(0.19, 1, 0.22, 1);
}
.card--cinematic:hover,
.card--cinematic:focus-within,
.card--cinematic:active {
--hc-scale: 1.12;
}
/* 6. Drift — diagonal pan, reveals a corner of the frame */
.card--drift {
--hc-scale: 1.12;
--hc-origin: 80% 80%;
--hc-transition-duration: 750ms;
--hc-transition-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.card--drift:hover,
.card--drift:focus-within,
.card--drift:active {
--hc-scale: 1.2;
--hc-translate-x: -6%;
--hc-translate-y: -6%;
}
/* 7. Push — inverts the pattern: image recedes as the card lifts */
.card--push {
--hc-scale: 1.1;
--hc-transition-duration: 500ms;
}
.card--push:hover,
.card--push:focus-within,
.card--push:active {
--hc-scale: 1;
}
/* 8. Colorize — grayscale to color */
.card--colorize {
--hc-filter: grayscale(100%) saturate(80%);
--hc-transition-duration: 800ms;
}
.card--colorize:hover,
.card--colorize:focus-within,
.card--colorize:active {
--hc-scale: 1.05;
--hc-filter: grayscale(0%) saturate(110%);
}
/* 9. Focus Pull — blur to sharp */
.card--unblur {
--hc-scale: 1.03;
--hc-filter: blur(3px) brightness(90%);
--hc-transition-duration: 500ms;
}
.card--unblur:hover,
.card--unblur:focus-within,
.card--unblur:active {
--hc-scale: 1.1;
--hc-filter: blur(0) brightness(100%);
}
/* 10. Spotlight — dim to bright */
.card--brighten {
--hc-filter: brightness(75%) contrast(95%);
--hc-transition-duration: 500ms;
}
.card--brighten:hover,
.card--brighten:focus-within,
.card--brighten:active {
--hc-scale: 1.06;
--hc-filter: brightness(100%) contrast(100%);
}
/* ============================================
ACCESSIBILITY
============================================ */
@media (prefers-reduced-motion: reduce) {
.card__img,
.card {
transition: none !important;
}
}