/* ==========================================================================
   Al-Azhar Academy — animations.css
   The ONLY file that defines motion. Structure and appearance live elsewhere.

   Rules honoured here:
   - Only `transform`, `opacity` and two registered custom properties (which
     drive the navigation pill's geometry) are animated. Shadows fade via a
     pseudo-element, never by animating box-shadow itself.
   - Decorative motion sits behind `prefers-reduced-motion: no-preference`, so
     "reduce" users simply get the finished state.
   - Hover effects require `(hover: hover) and (pointer: fine)`; touch devices
     get `:active` feedback instead.
   - Scroll triggers use IntersectionObserver and viewport changes use
     matchMedia (see assets/js/main.js). There are no scroll listeners and no
     resize handlers anywhere in this project.
   ========================================================================== */

/* -------------------------------------------------------------------------
   0. Registered geometry properties for the sliding navigation pill
   Registering them gives the browser a type, which is what makes them
   transitionable. Where `@property` is unsupported the pill still moves — it
   just resizes in one step instead of springing.
   ------------------------------------------------------------------------- */
@property --pill-x {
	syntax: '<length>';
	inherits: true;
	initial-value: 0px;
}

@property --pill-w {
	syntax: '<length>';
	inherits: true;
	initial-value: 0px;
}

/* -------------------------------------------------------------------------
   1. Reveal on scroll
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	html {
		scroll-behavior: smooth;
	}

	/* Gated on html.js so that without JavaScript the content is simply
	   visible instead of stuck at opacity 0. */
	html.js .reveal {
		opacity: 0;
		transform: translateY(12px);
		transition:
			opacity var(--dur-reveal) var(--ease),
			transform var(--dur-reveal) var(--ease);
	}

	html.js .reveal.is-visible {
		opacity: 1;
		transform: none;
	}

	/* Page transition: a short fade and an 8px slide on load. */
	.page-enter {
		animation: page-enter var(--dur-300) var(--ease) both;
	}

	@keyframes page-enter {
		from {
			opacity: 0;
			transform: translateY(8px);
		}
		to {
			opacity: 1;
			transform: none;
		}
	}
}

/* Mobile: fade only. Translation on small screens causes jank and
   contributes to motion sickness. */
@media (max-width: 47.99em) {
	@media (prefers-reduced-motion: no-preference) {
		html.js .reveal {
			transform: none;
		}
	}

	.stagger > .reveal {
		transition-delay: 0ms;
	}
}

/* -------------------------------------------------------------------------
   2. Stagger — set --i on children with JS
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	@media (min-width: 48em) {
		.stagger > .reveal {
			transition-delay: calc(var(--i, 0) * var(--stagger-step));
		}
	}
}

/* -------------------------------------------------------------------------
   3. Ambient motion — slow, almost invisible
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	@media (min-width: 48em) {
		.canvas::after {
			animation: ambient-drift var(--dur-ambient) var(--ease-in-out) infinite alternate;
		}
	}

	@keyframes ambient-drift {
		from {
			transform: translate3d(-1.5%, -1%, 0);
		}
		to {
			transform: translate3d(1.5%, 1%, 0);
		}
	}
}

/* -------------------------------------------------------------------------
   4. Navigation pill — the bouncy indicator under the desktop nav
   JS writes --pill-x and --pill-w on the rail; the pill inherits both. The
   spring curve overshoots slightly, which is what makes it feel physical.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	.nav__rail {
		transition:
			--pill-x var(--dur-spring) var(--ease-spring),
			--pill-w var(--dur-spring) var(--ease-spring);
	}
}

/* Measuring is not moving. JS drops this flag, parks, and sets it again in the
   same frame, so the very first measurement — and the correction that arrives
   when the webfonts land — land in place instead of springing in from a stale
   position. (Higher specificity than the rule above, so it wins.) */
.nav__rail:not([data-pill='ready']) {
	transition: none;
}

/* -------------------------------------------------------------------------
   5. Theme switch — the sun and crescent in the header
   ------------------------------------------------------------------------- */
.theme-toggle__glyph {
	transition:
		transform var(--dur-300) var(--ease-spring),
		opacity var(--dur-200) var(--ease);
}

.theme-toggle__ring {
	transition:
		transform var(--dur-300) var(--ease),
		opacity var(--dur-200) var(--ease);
}

/* -------------------------------------------------------------------------
   6. Buttons
   ------------------------------------------------------------------------- */
.btn {
	transition:
		background-color var(--dur-200) var(--ease),
		border-color var(--dur-200) var(--ease),
		color var(--dur-200) var(--ease),
		filter var(--dur-200) var(--ease),
		transform var(--dur-150) var(--ease);
}

.btn::after {
	transition: opacity var(--dur-200) var(--ease);
}

.link-arrow__icon {
	transition: transform var(--dur-200) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
	.btn:hover {
		transform: translateY(-1px);
	}

	.btn:hover::after {
		opacity: 1;
	}

	/* Brightening the gradient itself would mean swapping background-image,
	   which cannot be interpolated. A filter is smooth and stays on the
	   compositor instead. */
	.btn--primary:hover {
		filter: brightness(1.07) saturate(1.06);
	}

	.btn--secondary:hover {
		background-color: var(--accent-050);
		border-color: var(--accent-200);
	}

	.btn--inverse:hover {
		background-color: var(--white);
		border-color: var(--white);
		color: var(--ink-900);
	}

	.link-arrow:hover .link-arrow__icon {
		transform: translateX(3px);
	}
}

@media (hover: none), (pointer: coarse) {
	.btn:active {
		transform: scale(0.98);
	}
}

/* -------------------------------------------------------------------------
   7. Header condensation — the surface firms up as the page scrolls
   ------------------------------------------------------------------------- */
.site-header {
	transition: background-color var(--dur-200) var(--ease);
}

/* -------------------------------------------------------------------------
   8. Cards, tiles, quotes, campus cards
   ------------------------------------------------------------------------- */
.card,
.tile,
.campus-card,
.quote,
.flip-card {
	transition:
		transform var(--dur-200) var(--ease),
		border-color var(--dur-200) var(--ease);
}

.card::after,
.tile::after {
	transition: opacity var(--dur-200) var(--ease);
}

@media (prefers-reduced-motion: no-preference) {
	.campus-card__media img,
	.tile__media img,
	.media-frame img {
		transition: transform var(--dur-300) var(--ease);
	}
}

@media (hover: hover) and (pointer: fine) {
	.card:hover,
	.tile:hover,
	.campus-card:hover,
	.quote:hover {
		transform: translateY(-4px);
		border-color: var(--accent-200);
	}

	/* The gradient ring cannot be interpolated, so it swaps to the stronger
	   hairline. The lift and the shadow fade carry the motion. */
	.card:hover,
	.tile:hover,
	.campus-card:hover {
		background-image: linear-gradient(var(--surface), var(--surface)), var(--hairline-strong);
	}

	.card:hover::after,
	.tile:hover::after {
		opacity: 1;
	}

	.campus-card:hover .campus-card__media img,
	.tile:hover .tile__media img,
	.media-frame:hover img {
		transform: scale(1.03);
	}

	.footer__link:hover {
		color: var(--white);
	}
}

/* Inside the mobile sheet there is no sliding pill, so a link still needs a
   hover wash of its own. On desktop the pill supplies one. */
@media (max-width: 63.99em) and (hover: hover) and (pointer: fine) {
	.nav__link:hover {
		background-color: var(--surface-muted);
		color: var(--text);
	}
}

@media (hover: none), (pointer: coarse) {
	.card:active,
	.tile:active,
	.campus-card:active {
		transform: scale(0.99);
	}
}

/* -------------------------------------------------------------------------
   9. Flip cards — rotation only, and only on an explicit toggle
   ------------------------------------------------------------------------- */
.flip-card__group {
	transition: transform var(--dur-flip) var(--ease-back);
}

/* -------------------------------------------------------------------------
   10. Accordion (native <details>) — icon rotation only
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	.faq__icon {
		transition: transform var(--dur-200) var(--ease);
	}
}

/* -------------------------------------------------------------------------
   11. Mobile bottom-sheet navigation (slides up in 200ms)
   ------------------------------------------------------------------------- */
@media (max-width: 63.99em) {
	.nav {
		transition:
			transform var(--dur-200) var(--ease),
			opacity var(--dur-200) var(--ease),
			visibility 0s linear var(--dur-200);
	}

	.nav.is-open {
		transition-delay: 0s;
	}

	.nav-backdrop {
		transition: opacity var(--dur-200) var(--ease);
	}
}

/* -------------------------------------------------------------------------
   12. Quick-action button — the floating menu
   The child actions spring out with a short stagger, and retract together so
   closing always feels quick.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	.fab__trigger {
		transition: transform var(--dur-300) var(--ease-spring);
	}

	.fab__action {
		transition:
			transform var(--dur-300) var(--ease-spring),
			opacity var(--dur-200) var(--ease);
	}

	.fab.is-open .fab__action {
		transition-delay: calc(var(--i, 0) * 40ms);
	}

	.fab.is-open .fab__trigger {
		transform: rotate(135deg);
	}

	/* Opacity on top of the visibility switch in components.css, so the menu
	   fades out of the way instead of blinking. */
	.fab {
		transition: opacity var(--dur-200) var(--ease);
	}
}

/* -------------------------------------------------------------------------
   13. Form feedback — a notification card that slides in
   ------------------------------------------------------------------------- */
.form-status {
	transition:
		opacity var(--dur-200) var(--ease),
		transform var(--dur-200) var(--ease);
}

.form-status:not(:empty) {
	animation: status-in var(--dur-300) var(--ease-back) both;
}

@keyframes status-in {
	from {
		opacity: 0;
		transform: translateY(-6px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* -------------------------------------------------------------------------
   14. Safety net — when a visitor asks for less motion, ship the end state.
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
		scroll-behavior: auto !important;
	}

	.reveal,
	.page-enter {
		opacity: 1;
		transform: none;
	}

	.fab__action {
		transform: none;
	}
}
