/**
 * The motion layer, over Option A2's brick and granite.
 *
 * Weight settling into place, not bounce. Two gestures only: a short rise-and-
 * fade when a section first reaches the viewport, and a lift under the pointer
 * or the keyboard on anything you can click. Nothing parallaxes, nothing
 * scrubs, nothing overshoots.
 *
 * This file obeys design.css's two rules, unchanged:
 *   1. Colour comes from var(--ast-global-color-N), or color-mix() over those.
 *      No literal hex, rgb() or named colour except black and transparent
 *      inside color-mix(). The lift shadows below are the only new colour and
 *      they are mixes over the palette, so a Customizer change still reaches
 *      them.
 *   2. Hooks are className values (is-style-*, lehane-*, book-card*,
 *      filmtv-card*), never a Kadence uniqueID class.
 *
 * Sizes are px, as in design.css: this site's root font-size is 18px.
 *
 * ---------------------------------------------------------------------------
 * EVERYTHING IS INSIDE ONE `prefers-reduced-motion: no-preference` QUERY.
 *
 * That is what turns the whole layer off rather than merely shortening it, and
 * it is stronger than a `reduce` block that undoes things one property at a
 * time. Under `reduce` the hidden start state is not declared AT ALL, so there
 * is nothing to undo and no way for a missed override to strand content at
 * opacity 0. assets/js/motion.js makes the same check before it adds the gate
 * class, so the two agree; either one alone would still leave every page fully
 * visible.
 *
 * It also settles the mid-session case. Turn "reduce motion" on after load and
 * this query stops matching: the hidden state evaporates and everything not yet
 * revealed paints immediately. Turn it off after load and the gate class is
 * absent, so nothing hides retroactively.
 *
 * A browser too old to know the media feature matches neither `reduce` nor
 * `no-preference`. It gets a site with no motion and no hidden content, which
 * is the correct degradation.
 * ------------------------------------------------------------------------- */

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

	:root {
		/* Ease-out cubic. Fast at the start, long tail: the arc of something
		   heavy coming to rest. No overshoot, no elastic tail. */
		--lehane-ease-settle: cubic-bezier(0.22, 0.61, 0.36, 1);

		/* Short enough to read as settling rather than sliding in. */
		--lehane-reveal-rise: 16px;

		/* The lift shadows. Same two-layer shape as design.css's resting
		   --lehane-shadow-cover, with the second layer pushed further down and
		   opened up: the cover rises a few px and its shadow lengthens, which
		   is what a jacket lifted off a table actually does.

		   Declared at :root for the same reason design.css declares its pair
		   there: a custom property's var() references resolve where the
		   property is DECLARED, so these hold the root palette's colours even
		   when used inside granite, which remaps the slot variables. */
		--lehane-shadow-cover-lift:
			drop-shadow(0 2px 2px color-mix(in srgb, var(--ast-global-color-2) 22%, transparent))
			drop-shadow(0 18px 26px color-mix(in srgb, var(--ast-global-color-2) 26%, transparent));
		--lehane-shadow-on-dark-lift: drop-shadow(0 20px 30px color-mix(in srgb, black 55%, transparent));
	}

	/* -----------------------------------------------------------------------
	   Scroll and load reveals.

	   THE SELECTOR LIST BELOW IS CANONICAL AND IS COPIED IN assets/js/motion.js.
	   Change one and change the other. They cannot be derived from each other:
	   the CSS has to name the targets before any script has touched the
	   document, and the script has to find the same set to observe.

	   NO TARGET CONTAINS ANOTHER TARGET, and that is load bearing rather than
	   tidy. A reveal inside a reveal multiplies two opacity curves and two
	   translations, so a nested card would travel 32px and fade twice. The two
	   places this could happen are handled by construction: a card grid's
	   section contributes only its heading and its closing button, never its
	   `.kt-inside-inner-col`, and the book single contributes
	   `.writing-single__buy` and `__praise` but NOT `__related`, whose children
	   are `.book-card` and reveal on their own.

	   `.lehane-motion` is added to <html> by motion.js, synchronously, from a
	   blocking script in <head>. If that script never runs -- JS off, a 404, a
	   blocker, no IntersectionObserver -- this rule matches nothing and every
	   page renders exactly as it did before this file existed.

	   :where(), NOT :is(), AND THAT IS THE WHOLE REASON THE NEXT RULE WORKS.
	   :is() takes the specificity of its most specific argument, which here is
	   a three-class descendant selector, so `.lehane-motion :is(...)` computes
	   as (0,4,0) and beat the `.lehane-motion .is-revealed` rule below at
	   (0,2,0). Measured on the live homepage before this was changed: the
	   script observed all 26 targets, applied `is-revealed` and the staggered
	   delay to the three above the fold exactly as intended, and every one of
	   them stayed at opacity 0 with no transition ever starting. Nothing in the
	   console, nothing wrong in the JS. :where() contributes ZERO specificity,
	   so this rule is (0,1,0), the revealed rule outranks it, and a reader with
	   any site rule of their own on these elements outranks it too.
	   --------------------------------------------------------------------- */
	.lehane-motion :where(
		.lehane-page-lead,
		.lehane-news-header,
		.ast-archive-description,
		.lehane-books-section__title,
		.filmtv-header,
		.ast-article-single > .entry-header:not(.ast-no-title),
		:is(.lehane-lead, .lehane-about, .lehane-about-lead, .lehane-contact-band, .lehane-agents) > .kt-row-column-wrap,
		:is(.lehane-books, .lehane-filmtv-band, .lehane-news) .kt-inside-inner-col
			> :is(.wp-block-kadence-advancedheading, .wp-block-kadence-advancedbtn),
		.book-card,
		.filmtv-card,
		.lehane-news-list .loop-entry,
		#primary .ast-article-post,
		.writing-single__buy,
		.writing-single__praise
	) {
		opacity: 0;
		transform: translate3d(0, var(--lehane-reveal-rise), 0);
	}

	/* The revealed state is keyed on the class alone. `.is-revealed` is written
	   by motion.js and by nothing else on this site, so repeating the list
	   above would add twelve lines and no precision. It outranks the hidden
	   rule at (0,2,0) against (0,1,0) -- see the :where() note above, which is
	   what makes that true.

	   Opacity lands before the travel does: the text is readable while the last
	   few px of the rise are still running, which is why 420 and 560 are
	   different numbers. Both are on the generous side of a transition because
	   a whole section is moving; nothing here is a response to an input.

	   --lehane-reveal-delay is set inline per element by motion.js, and it is a
	   custom property rather than an inline `transition-delay` so it cannot
	   leak into any LATER transition on the same element. */
	.lehane-motion .is-revealed {
		opacity: 1;
		transform: none;
		transition:
			opacity 420ms var(--lehane-ease-settle) var(--lehane-reveal-delay, 0ms),
			transform 560ms var(--lehane-ease-settle) var(--lehane-reveal-delay, 0ms);
	}

	/* -----------------------------------------------------------------------
	   Book cards.

	   The cover's resting shadow is a `filter`, not a `box-shadow` -- design.css
	   puts it there because the grid clips nothing and a filter follows the
	   jacket's transparent edges. So the lift is a transform plus a filter
	   swap, and inside granite it swaps the on-dark token instead.

	   design.css already turns the title to slot 0 on hover; this only gives
	   that change a duration. Both the card link and the title rule use the
	   file's `:is(:hover, :focus-visible)` idiom, so tabbing to a card does
	   exactly what pointing at it does.
	   --------------------------------------------------------------------- */
	.book-card__cover img,
	.book-card__placeholder {
		transition:
			transform 260ms var(--lehane-ease-settle),
			filter 260ms var(--lehane-ease-settle);
	}

	.book-card__title {
		transition: color 180ms var(--lehane-ease-settle);
	}

	.book-card__link:is(:hover, :focus-visible) :is(.book-card__cover img, .book-card__placeholder) {
		transform: translate3d(0, -5px, 0);
	}

	.book-card__link:is(:hover, :focus-visible) .book-card__cover img {
		filter: var(--lehane-shadow-cover-lift);
	}

	/* Granite's own rule in design.css is (0,2,1); this is (0,4,1) and wins on
	   specificity, not on the order the two files happen to enqueue in. */
	:is(.lehane-granite, .is-style-granite)
		.book-card__link:is(:hover, :focus-visible) .book-card__cover img {
		filter: var(--lehane-shadow-on-dark-lift);
	}

	/* -----------------------------------------------------------------------
	   Film & TV cards.

	   THE CARD IS AN <li>, NOT A LINK, and it holds two links: the poster and
	   the title. `:has(:focus-visible)` is the keyboard half of the same idiom
	   design.css uses on single links, and it fires from either one, so tabbing
	   to the title lifts the poster above it. design.css already uses :has()
	   for the book grid, so this is not a new dependency.

	   The poster frame has `overflow: hidden` (the child theme's filmtv.css)
	   and `object-fit: cover` on the image, so scaling the image inside the
	   frame crops further in and never exposes an edge. The frame itself takes
	   the lift and, on granite, the deeper shadow -- design.css puts the filter
	   on the frame for exactly this reason, because a filter on the image would
	   be clipped by the frame's own overflow.

	   The image's 480ms is deliberately slower than the frame's 300ms. The
	   poster settles after the card does, which reads as depth rather than as
	   one flat object moving.
	   --------------------------------------------------------------------- */
	.filmtv-card__poster {
		transition:
			transform 300ms var(--lehane-ease-settle),
			filter 300ms var(--lehane-ease-settle);
	}

	.filmtv-card__poster img {
		transition: transform 480ms var(--lehane-ease-settle);
	}

	.filmtv-card__title a {
		transition: color 180ms var(--lehane-ease-settle);
	}

	.filmtv-card:is(:hover, :has(:focus-visible)) .filmtv-card__poster {
		transform: translate3d(0, -4px, 0);
	}

	.filmtv-card:is(:hover, :has(:focus-visible)) .filmtv-card__poster img {
		transform: scale(1.04);
	}

	:is(.lehane-granite, .is-style-granite)
		.filmtv-card:is(:hover, :has(:focus-visible)) .filmtv-card__poster {
		filter: var(--lehane-shadow-on-dark-lift);
	}

	/* -----------------------------------------------------------------------
	   Buttons, solid and ghost.

	   design.css owns both colour changes; this gives them a duration and adds
	   one px of travel. 1px is the whole gesture: a button on this site is a
	   flat rectangle with square corners, and anything more would make it read
	   as a chip. The :active rule puts it back down, so a click has a bottom.

	   The selector covers both shapes design.css documents -- the front end
	   renders `a.kt-button.wp-block-button__link.is-style-ghost`, the editor
	   renders the style class on the wrapper -- and both take the same lift.
	   --------------------------------------------------------------------- */
	.kt-button.wp-block-button__link,
	.is-style-ghost .kt-button.wp-block-button__link {
		transition:
			background-color 180ms var(--lehane-ease-settle),
			color 180ms var(--lehane-ease-settle),
			box-shadow 180ms var(--lehane-ease-settle),
			transform 160ms var(--lehane-ease-settle);
	}

	.kt-button.wp-block-button__link:is(:hover, :focus-visible),
	.is-style-ghost .kt-button.wp-block-button__link:is(:hover, :focus-visible) {
		transform: translate3d(0, -1px, 0);
	}

	.kt-button.wp-block-button__link:active,
	.is-style-ghost .kt-button.wp-block-button__link:active {
		transform: none;
	}

	/* -----------------------------------------------------------------------
	   The masthead menu.

	   A 2px rule wipes in under the label from the left. `scaleX` on a
	   pseudo-element, so nothing about the menu's box changes and the header
	   cannot shift.

	   THE HORIZONTAL INSET IS ASTRA'S OWN PADDING, not a guess: `.main-header-menu
	   .menu-link` is `padding: 0 1em` (astra/assets/css/minified/style.min.css),
	   measured as 16.875px against the item's 16.875px font. Matching it puts
	   the rule under the WORD rather than under the hit area, which is two or
	   three characters wider on each side.

	   THE VERTICAL OFFSET IS FROM THE MIDDLE, NOT FROM THE BOTTOM, and that is
	   the correction rather than the first idea. `bottom: -4px` reads as "just
	   under the text" only if the box hugs the text. It does not here: the
	   desktop header builder gives this link `display: flex` and
	   `line-height: 80px`, so the box is the full 80px height of the masthead
	   and the glyphs are centred inside it. Measured on the live page, that put
	   the rule at y=116 in a masthead that ends at y=112 -- on top of the brick
	   bottom border, 36px below the word it belongs to. The line box is centred
	   on the element, so half of it plus a gap, in em, tracks the type at any
	   size: 0.75em is roughly 6px under the baseline of this 16.875px font.

	   SCOPED TO #ast-desktop-header AND TO TOP-LEVEL ITEMS. The break-point
	   menu re-pads the same links to `0 20px` at full width, where a wiping
	   rule under a stacked list item would read as a loading bar; and the Books
	   submenu is a panel of its own, where an underline belongs even less.

	   currentColor, so the rule is whatever the item's text is at that moment --
	   the granite masthead's on-dark label colour on hover, with no colour of
	   its own to keep in step with the Customizer.
	   --------------------------------------------------------------------- */
	#ast-desktop-header .ast-builder-menu-1 .main-header-menu > .menu-item > .menu-link {
		position: relative;
		/* Astra sets `transition: all .2s linear` on every menu link. Linear on
		   a pointer response is the one easing that reads as machinery. */
		transition: color 200ms var(--lehane-ease-settle);
	}

	#ast-desktop-header .ast-builder-menu-1 .main-header-menu > .menu-item > .menu-link::after {
		content: '';
		position: absolute;
		left: 1em;
		right: 1em;
		top: calc(50% + 0.75em);
		height: 2px;
		background-color: currentColor;
		transform: scaleX(0);
		transform-origin: left center;
		transition: transform 260ms var(--lehane-ease-settle);
	}

	#ast-desktop-header .ast-builder-menu-1 .main-header-menu > .menu-item:is(:hover, :focus-within) > .menu-link::after,
	#ast-desktop-header .ast-builder-menu-1 .main-header-menu > .menu-item > .menu-link:focus-visible::after {
		transform: scaleX(1);
	}

	/* -----------------------------------------------------------------------
	   Text links.

	   design.css and Astra already own every colour here. These rules add the
	   duration and nothing else, so a link never snaps between two colours.
	   The prose rule is scoped to paragraphs and list items so it cannot reach
	   a card link or a button that happens to sit inside .entry-content.

	   DESCENDANT, NOT `> a`. The child combinator was written first and missed
	   most of the news singles: their links sit inside the <em> the migration
	   brought over, e.g. `p > em > a`, so the rule matched nothing there and
	   Astra's `a { transition: .2s linear }` kept the colour change. Measured on
	   /creative-loafing-this-is-how-dennis-lehane-succeeds-as-a-writer/.
	   --------------------------------------------------------------------- */
	.lehane-news-list .entry-title a,
	#primary .ast-article-post .entry-title a,
	.entry-content :is(p, li) a {
		transition: color 180ms var(--lehane-ease-settle);
	}
}
