/*
 * Motion.
 *
 * Every animated rule on the site lives here, loaded after the layout sheets
 * so it refines what they have settled, and before buttons.css, which stays
 * the one authority on how a button looks. Two rules hold throughout:
 * only `transform` and `opacity` are animated, because anything else makes the
 * page recalculate layout while it scrolls; and every effect is switched off
 * under `prefers-reduced-motion`, which is decision #34.
 *
 * Nothing in here may hide content from a reader with JavaScript off: an
 * element that starts invisible is revealed by a class that JavaScript adds,
 * so a page that never runs the script shows everything.
 */

/*
 * Structure of this file
 *
 * Every effect sits inside `@media (prefers-reduced-motion: no-preference)`,
 * so under `reduce` none of it exists and the earlier sheets apply untouched
 * (a text link keeps its instant underline, for example). A short `reduce`
 * block near the end additionally forces any state class that JavaScript has
 * already added back to its resting look, in case the preference changes
 * while the page is open. A `print` block does the same, so nothing that is
 * waiting for a scroll is left blank on paper.
 *
 * The only properties animated are `transform` and `opacity`, with two
 * exceptions that are declared where they occur: `filter` for the blur-up
 * (item 3), and the `background-color` / `border-color` transitions on the
 * slider arrows, which are copied unchanged from experience.css because a
 * `transition` declaration replaces rather than adds to the one it overrides.
 */

/* Not an effect: the wrapper that carries the banner zoom must fill its
   parent exactly as the photograph did, whether or not motion is allowed. */
.home-hero__zoom { display: block; width: 100%; height: 100%; }

@keyframes mo-kenburns {
	from { transform: scale(1); }
	to { transform: scale(1.04); }
}

@media (prefers-reduced-motion: no-preference) {

	:root { --mo-ease: cubic-bezier(.2, .7, .2, 1); }

	/* 1. Ken Burns, home banner only. The scale is on a wrapper, not on the
	   <img>: home-hero.js cancels every animation on the image each time the
	   banner changes, which would snap a zoom on the image back to 1. The
	   banner box (.home-hero__media) clips at overflow: hidden, so the extra
	   4% can never widen the page. One run, then it holds at 1.04. */
	.home-hero__zoom {
		animation: mo-kenburns 14s cubic-bezier(.25, .1, .25, 1) 1 both;
	}

	/* 2. Image wipe. A panel in the section's ground colour (set from
	   JavaScript as --mo-ground) covers the picture and retracts upward by
	   scaling to nothing from its top edge, so no overflow clip is needed on
	   the host (the slab figure must not clip: the magnifier lens leaves it).
	   The class is added by script only, so with JS off the picture is never
	   covered. */
	.mo-wipe-host { position: relative; }
	.mo-wipe-host::after {
		content: "";
		position: absolute;
		inset: 0;
		z-index: 2;
		background: var(--mo-ground, #fcfbf8);
		transform: scaleY(1);
		transform-origin: 50% 0;
		pointer-events: none;
	}
	.mo-wipe-host.is-revealed::after {
		transform: scaleY(0);
		transition: transform 900ms cubic-bezier(.7, 0, .2, 1);
	}
	.mo-wipe-host.is-revealed.mo-instant::after { transition: none; }
	:focus-within > .mo-wipe-host::after { transform: scaleY(0); }

	/* The Spaces cards are also faded in by home-reveal.js. For the picture to
	   be uncovered rather than faded in, the card itself stays opaque and the
	   fade moves to what is under the picture (label and title). */
	.home-space.home-reveal-pending:has(> .mo-wipe-host) { opacity: 1; transform: none; }
	.home-space.home-reveal-pending:has(> .mo-wipe-host) > :not(.mo-wipe-host) { opacity: 0; transform: translateY(20px); }
	.home-space.home-reveal-ready:has(> .mo-wipe-host) > :not(.mo-wipe-host) { transition: opacity 650ms ease-out, transform 650ms ease-out; }
	.home-space.home-reveal-pending:has(> .mo-wipe-host):focus-within > :not(.mo-wipe-host) { opacity: 1; transform: none; }

	/* 3. Blur-up. `filter` is the one property here that is neither
	   transform nor opacity; it runs once per image, on the compositor, and
	   only for images that were still loading when the script ran. The
	   transform entry keeps the hover zoom's own transition intact. */
	img.mo-blur-up { transition: filter 700ms ease-out, transform 600ms ease; }
	img.mo-blur-up.is-blurred { filter: blur(10px); }

	/* 4. Collection cards: the caption lifts, the arrow slides diagonally.
	   The picture's 1.025 zoom stays in home.css. */
	.home-collection__caption > div,
	.home-collection__arrow,
	.collection-paths__grid a > span:first-of-type,
	.collection-paths__grid a > span:last-of-type {
		transition: transform 320ms var(--mo-ease);
	}
	.home-collection:focus-visible .home-collection__caption > div,
	.collection-paths__grid a:focus-visible > span:first-of-type { transform: translateY(-4px); }
	.home-collection:focus-visible .home-collection__arrow,
	.collection-paths__grid a:focus-visible > span:last-of-type { transform: translate(4px, -4px); }
}

@media (prefers-reduced-motion: no-preference) and (hover: hover) {
	.home-collection:hover .home-collection__caption > div,
	.collection-paths__grid a:hover > span:first-of-type { transform: translateY(-4px); }
	.home-collection:hover .home-collection__arrow,
	.collection-paths__grid a:hover > span:last-of-type { transform: translate(4px, -4px); }
}

/* 5. Slider arrows slide in ~8px from their edge as they fade in. The fade
   itself stays in experience.css / product-gallery.css; only the resting
   offset and the slide are added. It lives inside the same hover + fine
   pointer query as the fade, so on touch, where the arrows are always shown,
   they are never left offset. The background-color / border-color entries are
   carried over unchanged from experience.css (see the note at the top). */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.carousel__controls button[data-carousel-previous],
	.product-gallery__arrows button[data-gallery-prev] { transform: translateX(-8px); }
	.carousel__controls button[data-carousel-next],
	.product-gallery__arrows button[data-gallery-next] { transform: translateX(8px); }
	.carousel__controls button[data-carousel-previous],
	.carousel__controls button[data-carousel-next] {
		transition: transform 280ms var(--mo-ease), background-color 200ms ease, border-color 200ms ease;
	}
	.product-gallery__arrows button[data-gallery-prev],
	.product-gallery__arrows button[data-gallery-next] { transition: transform 280ms var(--mo-ease); }
	.carousel:hover > .carousel__controls button,
	.carousel:focus-within > .carousel__controls button,
	.product-gallery__main:hover .product-gallery__arrows button,
	.product-gallery__main:focus-within .product-gallery__arrows button { transform: none; }
}

/* 6 and 8 are NOT motion: a dimmed card at the edge and a 2px progress line
   are the two things that told a reader "there is more along this row" before
   the scrollbar was taken away. Switching them off for someone who asked for
   less movement would leave exactly that reader with no indicator at all, so
   the resting STATES live out here and only their transitions are gated.

   6. Cards still to come. The dimming is on the card's IMAGE, never on its
   caption: the agent that first drew this dimmed the whole card, which put
   real text at 55% and under the contrast floor. A card under the pointer or
   holding focus is shown in full. home-reveal.js owns the card's own opacity
   and transform, so this goes on the child. */
[data-carousel-track] > .mo-card.is-edge > :is(.home-collection__image, img) { opacity: .55; }
[data-carousel-track] > .mo-card.is-edge:hover > :is(.home-collection__image, img),
[data-carousel-track] > .mo-card.is-edge:focus-within > :is(.home-collection__image, img) { opacity: 1; }

/* 8. Scroll progress where the scrollbar used to be: 2px, absolutely placed
   in the track's own bottom padding so it adds no height, driven by transform
   only. [hidden] (set by script) removes it when the row does not overflow. */
.mo-progress {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 2px;
	pointer-events: none;
}
.mo-progress > span {
	display: block;
	height: 100%;
	background: var(--elevare-line);
	transform: scaleX(0);
	transform-origin: 0 50%;
}
.mo-progress.is-rtl > span { transform-origin: 100% 50%; }

@media (prefers-reduced-motion: no-preference) {
	[data-carousel-track] > .mo-card > :is(.home-collection__image, img) { transition: opacity 300ms ease; }
}

/* 7. Drag to scroll with a mouse. An input method, not an animation, so it is
   not gated on the motion preference either. Cursor and selection feedback. */
@media (hover: hover) and (pointer: fine) {
	[data-carousel-track].mo-draggable { cursor: grab; }
	[data-carousel-track].mo-draggable a { cursor: pointer; }
	[data-carousel-track].is-dragging,
	[data-carousel-track].is-dragging * { cursor: grabbing !important; }
	[data-carousel-track].is-dragging {
		user-select: none;
		-webkit-user-select: none;
		scroll-snap-type: none;
		scroll-behavior: auto;
	}
	[data-carousel-track].is-dragging a { pointer-events: none; }
}

@media (prefers-reduced-motion: no-preference) {

	/* 9. Headings, line by line. Before a heading is split it is only
	   transparent (class added by script); when it scrolls in, script wraps
	   each rendered line, runs the slide, then puts the original markup back.
	   The clip is deliberately generous so descenders and ascenders are never
	   cut at the tight line-heights these headings use. */
	.mo-h-pending { opacity: 0; }
	.mo-split .mo-line { display: block; clip-path: inset(-.3em -.2em); }
	/* The cover's h1 states its own lines as spans, so those spans become the
	   moving part. They are block-level already; the wrapper only provides the
	   clip, and must not introduce a second box model. */
	.mo-split .mo-line > .mo-line__in { display: block; }
	.mo-split .mo-line__in {
		display: block;
		opacity: 0;
		transform: translateY(110%);
		transition: transform 850ms var(--mo-ease), opacity 600ms ease-out;
		transition-delay: calc(var(--mo-i, 0) * 60ms);
	}
	.mo-split.mo-in .mo-line__in { opacity: 1; transform: none; }
	/* A plain heading rises as one block: no measuring, so no chance of it
	   recording the wrong number of lines and keeping it. */
	.mo-h-block {
		opacity: 0;
		transform: translateY(24px);
		transition: transform 850ms var(--mo-ease), opacity 600ms ease-out;
	}
	.mo-h-block.mo-in { opacity: 1; transform: none; }

	/* 10. Text links: the underline grows from the left. The old hover
	   underline is switched off here and replaced by a 1px bar. It sits at
	   about where the text underline did (centre of the 44px link plus the
	   baseline drop and the 5px offset) - an approximation, since the exact
	   baseline depends on the font. The permanent border on .study-text-link
	   is left alone. */
	.home-link,
	.study-text-link { position: relative; }
	.home-link::after,
	.study-text-link::after {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		top: calc(50% + 10px);
		height: 1px;
		background: currentColor;
		transform: scaleX(0);
		transform-origin: 0 50%;
		transition: transform 340ms var(--mo-ease);
		pointer-events: none;
	}
	.home-link:hover,
	.study-text-link:hover { text-decoration: none; }
	.home-link:hover::after,
	.study-text-link:hover::after { transform: scaleX(1); }

	/* 11. Specification band: columns 50ms apart. */
	.mo-spec-pending .product-specs__item { opacity: 0; transform: translateY(14px); }
	.mo-spec-ready .product-specs__item {
		transition: opacity 600ms ease-out, transform 600ms ease-out;
		transition-delay: calc(var(--mo-i, 0) * 50ms);
	}
	.mo-spec-pending .product-specs__item:focus-within { opacity: 1; transform: none; }

	/* 12. Eyebrow labels. Letter-spacing is a layout property, so it is not
	   animated. The label starts 4% wider and fades in while settling to its
	   true width: a horizontal scale from the left edge, which widens the
	   spaces between letters (and, slightly, the letters). An approximation
	   of tracking that settles, not true tracking. */
	.mo-eb-pending { opacity: 0; transform: scaleX(1.04); transform-origin: 0 50%; }
	.mo-eb-ready { transition: opacity 700ms ease-out, transform 900ms var(--mo-ease); }
}

/* Preference changed to reduce while the page is open, and print: whatever
   script has already hidden or covered is shown, with no transition. */
@media (prefers-reduced-motion: reduce), print {
	.mo-h-pending, .mo-eb-pending, .mo-h-block { opacity: 1 !important; transform: none !important; }
	.mo-spec-pending .product-specs__item { opacity: 1 !important; transform: none !important; }
	.mo-split .mo-line__in { opacity: 1 !important; transform: none !important; transition: none !important; }
	.mo-wipe-host::after { display: none !important; }
	img.is-blurred { filter: none !important; }
	.home-hero__zoom { animation: none !important; }
	.home-space.home-reveal-pending:has(> .mo-wipe-host) > :not(.mo-wipe-host) { opacity: 1; transform: none; }
}

/* --- The mobile menu ------------------------------------------------- *
 * The menu was switched with `display`, which cannot be animated: it simply
 * appeared. It still uses display, because a menu that is closed should take
 * no room and hold no tab stop — but the change is carried across with
 * `transition-behavior: allow-discrete` and `@starting-style`, so the panel
 * fades and settles instead of snapping. A browser without those two keeps
 * exactly the behaviour it has today, which is the old instant open; nothing
 * is hidden from it.
 */
@media (max-width: 782px) and (prefers-reduced-motion: no-preference) {
	.main-navigation {
		opacity: 0;
		transform: translateY(-0.5rem);
		transition: opacity 260ms ease, transform 320ms cubic-bezier(.22, 1, .36, 1), display 320ms allow-discrete;
	}

	.main-navigation.is-open {
		opacity: 1;
		transform: none;
	}

	@starting-style {
		.main-navigation.is-open {
			opacity: 0;
			transform: translateY(-0.5rem);
		}
	}

	/* The items arrive just behind the panel, one after another. Six is as far
	   as it is worth going; beyond that the last item waits noticeably. */
	.main-navigation.is-open li {
		animation: mo-menu-item 380ms cubic-bezier(.22, 1, .36, 1) both;
	}
	.main-navigation.is-open li:nth-child(1) { animation-delay: 60ms; }
	.main-navigation.is-open li:nth-child(2) { animation-delay: 105ms; }
	.main-navigation.is-open li:nth-child(3) { animation-delay: 150ms; }
	.main-navigation.is-open li:nth-child(4) { animation-delay: 195ms; }
	.main-navigation.is-open li:nth-child(5) { animation-delay: 240ms; }
	.main-navigation.is-open li:nth-child(n + 6) { animation-delay: 285ms; }

	@keyframes mo-menu-item {
		from { opacity: 0; transform: translateY(-0.4rem); }
		to { opacity: 1; transform: none; }
	}
}

/* The three bars become a cross. Transform only, and it runs whether or not
   the panel itself can animate, so the button always answers the press. */
@media (prefers-reduced-motion: no-preference) {
	.nav-toggle__bar,
	.nav-toggle__bar::before,
	.nav-toggle__bar::after {
		transition: transform 300ms cubic-bezier(.22, 1, .36, 1), opacity 180ms ease;
	}
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar { transform: translate(-50%, -50%) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::before { transform: translate(-50%, 6px) rotate(-90deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::after { transform: translate(-50%, -6px) rotate(-90deg); opacity: 0; }
