.project-card {
	position: relative;
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.6s cubic-bezier(0.215, 0.61, 0.355, 1);
	display: flex;
	justify-content: center;
	align-items: center;
	/* Grid items default to `min-width: auto`, letting the image's own
	   intrinsic width override the 1fr track size at narrower viewports
	   — the card (and its hover tint) then overflows the track by a
	   pixel or two, invisible until the accent color reveals it. */
	min-width: 0;
}

/* A single-media project has nothing to open in the lightbox, so it
   shouldn't look clickable either. */
.project-card.is-single-media {
	cursor: default;
}

.project-card.visible {
	opacity: 1;
}

.card-image-wrap {
	display: block;
	position: relative;
	overflow: hidden;
	background: var(--color-light-gray);
	width: 100%;
	height: auto;
	user-select: none;
	-webkit-user-select: none;
}

.card-image-wrap picture {
	display: block;
}

/* ── LOADING SPINNER: shown while a card's image/video is still fetching ── */
.card-image-wrap::before {
	content: "";
	position: absolute;
	z-index: 2;
	top: 50%;
	left: 50%;
	width: 24px;
	height: 24px;
	margin: -12px 0 0 -12px;
	border: 2px solid rgba(0, 0, 0, 0.15);
	border-top-color: var(--primary);
	border-radius: 50%;
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.2s ease;
}

.card-image-wrap.is-loading::before {
	opacity: 1;
	animation: card-spin 0.7s linear infinite;
}

@keyframes card-spin {
	to {
		transform: rotate(360deg);
	}
}

.card-image-wrap img,
.card-image-wrap video {
	width: 100%;
	height: auto;
	display: block;
	transition: transform 0.8s ease-out;
	pointer-events: none;
}

.downloads-grid .card-image-wrap {
	cursor: default;
}

.card-download-btn {
	position: absolute;
	z-index: 1;
	right: 8px;
	bottom: 8px;
	width: 28px;
	height: 28px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--primary);
	color: #fff;
	border-radius: 50%;
	opacity: 0;
	transform: translateY(100%);
	transition:
		transform 0.25s ease,
		opacity 0.25s ease;
}

.downloads-grid .card-image-wrap:hover .card-download-btn {
	opacity: 1;
	transform: translateY(0);
}

.card-video-toggle {
	position: absolute;
	z-index: 1;
	right: 8px;
	bottom: 8px;
	width: 28px;
	height: 28px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--primary);
	color: #fff;
	border: none;
	border-radius: 50%;
	cursor: pointer;
}

.card-video-toggle .icon-play,
.card-video-toggle .icon-pause {
	display: flex;
	align-items: center;
	justify-content: center;
}

.card-video-toggle svg {
	display: block;
}

/* The play triangle's own shape isn't centered in its viewBox — nudge it
   left so it lands on the optical center of the button, not the
   bounding-box center. */
.card-video-toggle .icon-play svg {
	transform: translateX(-1px);
}

.card-video-toggle .icon-pause {
	display: none;
}

.card-image-wrap.is-playing .icon-play {
	display: none;
}

.card-image-wrap.is-playing .icon-pause {
	display: flex;
}

/* ── HOVER TINT: flat accent-color recolor for SVG thumbnails ── */
.card-image-wrap.is-svg-thumb::after {
	content: "";
	position: absolute;
	inset: 0;
	background-color: var(--primary);
	-webkit-mask-image: var(--thumb-mask);
	-webkit-mask-size: 100% 100%;
	-webkit-mask-repeat: no-repeat;
	/* Without this, the mask reads the SVG's luminance (black = dark =
	   masked out) instead of its alpha, so only the anti-aliased edges
	   of a black shape stay visible and the fill reads through as the
	   original black instead of solid accent color. */
	-webkit-mask-mode: alpha;
	mask-image: var(--thumb-mask);
	mask-size: 100% 100%;
	mask-repeat: no-repeat;
	mask-mode: alpha;
	opacity: 0;
	transition: opacity 0.25s ease;
	pointer-events: none;
}

.card-image-wrap.is-svg-thumb:hover::after {
	opacity: 1;
}

/* ── HOVER TINT: recolor photo thumbnails to a single accent hue ──
   `mix-blend-mode: color` can't touch true black/white (luminosity at
   those extremes stays fixed no matter the hue laid on top), so a
   high-contrast photo would still show black through the tint. `screen`
   against an accent-colored backdrop doesn't have that limit — grayscale
   black becomes exactly the backdrop color, white stays white.

   The blend used to run on the <img> itself, tinted via the parent's
   background-color. mix-blend-mode can't transition (it's a discrete
   property — browsers just flip it), so toggling it on :hover made the
   full-color image flash through screen-blended-with-purple for an
   instant before the grayscale filter finished easing in. Worse, the
   img's rendered box doesn't always cover the wrap's exactly (sub-pixel
   rounding), so the untinted background-color could show through as a
   thin frame right at the edge.

   Both problems go away by keeping the blend on an ::after overlay that
   is always screen-blended and always covers the wrap exactly (inset:
   0, same box, no dependency on the img's own rendered size) — only its
   opacity animates, which (unlike mix-blend-mode) genuinely transitions. */
.card-image-wrap.is-photo-thumb img {
	transition:
		transform 0.8s ease-out,
		filter 0.3s ease;
}

.card-image-wrap.is-photo-thumb:hover img {
	filter: grayscale(1);
}

.card-image-wrap.is-photo-thumb::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--primary);
	mix-blend-mode: screen;
	opacity: 0;
	transition: opacity 0.3s ease;
	pointer-events: none;
}

.card-image-wrap.is-photo-thumb:hover::after {
	opacity: 1;
}

.card-media-count {
	position: absolute;
	z-index: 1;
	bottom: 4px;
	right: 4px;
	width: 28px;
	height: 28px;
	background: var(--primary);
	color: #fff;
	border-radius: 999px;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: background 0.15s;
}

.card-image-wrap:hover .card-media-count {
	background: var(--color-dark);
}

.card-media-count svg {
	width: 14px;
	height: 14px;
	display: block;
}

/* ── Overview grid: thumbnail + caption stacked in one link ── */
.card-link {
	display: flex;
	flex-direction: column;
	width: 100%;
}

.card-caption {
	display: block;
	margin-top: 0.5rem;
	font-size: max(0.8rem, calc(var(--text-scale) * 0.4));
	line-height: 1.3;
	letter-spacing: var(--text-ls);
	color: var(--color-dark);
}
