/* ------------------------------------------------------------- websites --- */

.gb-ws-intro {
	max-width: 640px;
	margin: 0 0 90px;
}

.gb-ws-intro .gb-eyebrow {
	margin: 0 0 14px;
}

.gb-ws-intro .gb-lead {
	margin: 0;
}

.gb-ws-list {
	display: grid;
	gap: 160px;
}

.gb-ws-project {
	display: grid;
	grid-template-columns: minmax(0, 650px) minmax(0, 1fr);
	gap: 70px;
	align-items: center;
}

.gb-ws-project:nth-child(even) {
	grid-template-columns: minmax(0, 1fr) minmax(0, 650px);
}

.gb-ws-project:nth-child(even) .gb-ws-project__copy {
	order: 2;
}

.gb-ws-project__copy h4 {
	margin: 8px 0 22px;
	font-size: 28px;
	line-height: 36px;
}

.gb-ws-project__copy p {
	margin: 0 0 26px;
	color: var(--gb-muted);
}

.gb-ws-project__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 14px;
}

/* The anchor inherits the body's 28px line-height while <button> defaults to
   `normal`, so left alone the two render at different heights. Pin both. */
.gb-ws-project__actions .gb-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 10px;
	margin-top: 0;
	padding: 16px 34px;
	line-height: 1;
}

.gb-btn__play-icon {
	flex: none;
}

.gb-btn__play-icon {
	width: 0;
	height: 0;
	border-style: solid;
	border-width: 5px 0 5px 8px;
	border-color: transparent transparent transparent currentColor;
}

/* ----------------------------------------------------------- scroll reveal --- */

/* Copy and card enter from opposite sides so the pair assembles itself.
   .is-revealed is added once by tilt.js when the block scrolls into view. */
[data-gb-reveal] {
	opacity: 0;
	transform: translateY(40px);
	transition: opacity 0.9s cubic-bezier(0.22, 0.7, 0.2, 1),
		transform 0.9s cubic-bezier(0.22, 0.7, 0.2, 1);
}

[data-gb-reveal].is-revealed {
	opacity: 1;
	transform: translateY(0);
}

/* Copy slides in from the text side. The tilt only fades — its transform
   belongs to tilt.js, so animating it here would fight the pointer tracking. */
.gb-ws-project .gb-ws-project__copy {
	opacity: 0;
	transform: translateX(-30px);
	transition: opacity 0.9s cubic-bezier(0.22, 0.7, 0.2, 1) 0.15s,
		transform 0.9s cubic-bezier(0.22, 0.7, 0.2, 1) 0.15s;
}

.gb-ws-project:nth-child(even) .gb-ws-project__copy {
	transform: translateX(30px);
}

.gb-ws-project .gb-tilt {
	opacity: 0;
	transition: opacity 0.9s cubic-bezier(0.22, 0.7, 0.2, 1) 0.25s,
		transform 0.4s ease;
}

.gb-ws-project.is-revealed .gb-ws-project__copy {
	opacity: 1;
	transform: translateX(0);
}

.gb-ws-project.is-revealed .gb-tilt {
	opacity: 1;
}

/* Restate after the reveal rule above, which otherwise wins on source order
   and would re-introduce a transform transition during pointer tracking. */
.gb-ws-project .gb-tilt.is-active {
	transition: opacity 0.9s cubic-bezier(0.22, 0.7, 0.2, 1) 0.25s;
}

@media (prefers-reduced-motion: reduce) {
	[data-gb-reveal],
	.gb-ws-project .gb-ws-project__copy,
	.gb-ws-project .gb-tilt {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* Tilt stage. perspective lives on the wrapper so child layers share one
   vanishing point; the card itself only ever receives rotate transforms
   from tilt.js, translation lives on the inner layers for parallax. */
.gb-tilt {
	position: relative;
	display: inline-block;
	max-width: 100%;
	transform-style: preserve-3d;
	transition: transform 0.4s ease;
	will-change: transform;
}

/* No transition while the pointer is actively moving over the card, so
   the tilt tracks the cursor 1:1 instead of chasing it. */
.gb-tilt.is-active {
	transition: none;
}

.gb-tilt__layer {
	transition: transform 0.4s ease;
	will-change: transform;
}

.gb-tilt.is-active .gb-tilt__layer {
	transition: none;
}

.gb-tilt__layer--card img {
	display: block;
	max-width: 100%;
	height: auto;
	border-radius: 6px;
	box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
}

.gb-tilt__layer--text {
	position: absolute;
	left: 8%;
	right: 8%;
	bottom: 8%;
	display: flex;
	flex-direction: column;
	padding: 18px 22px;
	pointer-events: none;
	background: rgba(10, 10, 12, 0.55);
	border: 1px solid rgba(255, 255, 255, 0.14);
	border-radius: 10px;
	backdrop-filter: blur(14px) saturate(140%);
	-webkit-backdrop-filter: blur(14px) saturate(140%);
	box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
}

.gb-tilt__title {
	margin: 0;
	font-size: 22px;
	line-height: 28px;
	color: var(--gb-ink);
	text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}

.gb-tilt__tagline {
	margin: 6px 0 0;
	font-size: 13px;
	line-height: 20px;
	color: rgba(255, 255, 255, 0.8);
	text-transform: none;
	font-family: var(--gb-font-label);
	font-weight: 400;
	text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}

.gb-tilt__shine {
	position: absolute;
	inset: 0;
	background: transparent;
	pointer-events: none;
}

/* -------------------------------------------------------------- lightbox --- */

/* The stage carries the perspective so the frame can swing in on rotateX
   rather than just scaling. JS toggles .is-open and waits for the frame's
   transitionend before setting [hidden], so the exit actually plays. */
.gb-lightbox {
	position: fixed;
	inset: 0;
	z-index: 900;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--gb-edge);
	perspective: 1400px;
	/* Plain background-color fade, no backdrop-filter: blurring the whole
	   viewport forces everything behind the dialog to repaint every frame.
	   Fading background rather than opacity keeps the video fully opaque. */
	background: rgba(0, 0, 0, 0);
	transition: background-color 0.35s ease;
}

/* display:flex above would defeat the hidden attribute, so restate it. */
.gb-lightbox[hidden] {
	display: none;
}

.gb-lightbox.is-open {
	background: rgba(0, 0, 0, 0.95);
}

.gb-lightbox__frame {
	position: relative;
	width: 100%;
	max-width: 1100px;
	aspect-ratio: 16 / 9;
	background: #000;
	border-radius: 8px;
	overflow: hidden;
	opacity: 0;
	transform: translateY(60px) scale(0.9) rotateX(14deg);
	box-shadow: 0 40px 90px rgba(0, 0, 0, 0.7);
	transition: opacity 0.45s cubic-bezier(0.22, 0.7, 0.2, 1),
		transform 0.55s cubic-bezier(0.22, 0.9, 0.24, 1);
}

.gb-lightbox.is-open .gb-lightbox__frame {
	opacity: 1;
	transform: translateY(0) scale(1) rotateX(0deg);
}

/* A hairline sweep that races across the frame as it lands. */
.gb-lightbox__frame::after {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	background: linear-gradient(
		105deg,
		transparent 40%,
		rgba(255, 255, 255, 0.16) 50%,
		transparent 60%
	);
	background-size: 300% 100%;
	background-position: 140% 0;
	opacity: 0;
}

.gb-lightbox.is-open .gb-lightbox__frame::after {
	animation: gb-lb-sweep 1s ease 0.25s 1;
}

@keyframes gb-lb-sweep {
	0% {
		opacity: 1;
		background-position: 140% 0;
	}
	100% {
		opacity: 0;
		background-position: -40% 0;
	}
}

.gb-lightbox__iframe {
	width: 100%;
	height: 100%;
	border: 0;
}

.gb-lightbox__close {
	position: absolute;
	top: var(--gb-edge);
	right: var(--gb-edge);
	width: 44px;
	height: 44px;
	padding: 0;
	background: none;
	border: 0;
	color: var(--gb-ink);
	font-size: 34px;
	line-height: 1;
	cursor: pointer;
	opacity: 0;
	transform: rotate(-90deg);
	transition: color 0.3s ease, opacity 0.4s ease 0.2s,
		transform 0.4s cubic-bezier(0.22, 0.7, 0.2, 1) 0.2s;
}

.gb-lightbox.is-open .gb-lightbox__close {
	opacity: 1;
	transform: rotate(0deg);
}

.gb-lightbox__close:hover {
	color: var(--gb-muted);
	transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
	.gb-lightbox,
	.gb-lightbox__frame,
	.gb-lightbox__close {
		transition-duration: 0.01ms;
	}

	.gb-lightbox__frame {
		transform: none;
	}

	.gb-lightbox.is-open .gb-lightbox__frame::after {
		animation: none;
	}
}

@media (max-width: 1024px) {
	.gb-ws-project,
	.gb-ws-project:nth-child(even) {
		grid-template-columns: minmax(0, 1fr);
		gap: 40px;
	}

	.gb-ws-project:nth-child(even) .gb-ws-project__copy {
		order: 0;
	}

	.gb-ws-list {
		gap: 90px;
	}
}
