/**
 * motion.css — the shared visual and motion primitives.
 *
 * Five modules build on this file (header, search, cart, hero, and whatever
 * the product-grid pass adds). Everything here is defined EXACTLY ONCE and
 * consumed as a class. If you find yourself re-declaring a backdrop-filter or
 * a conic-gradient inside a component stylesheet, stop: the primitive belongs
 * here and the component belongs on the class.
 *
 * The public surface of this file is five classes and one utility:
 *
 *   .bedi-glass         frosted panel   — fill, hairline, blur, opaque fallback
 *   .bedi-glow          neon border     — animated only while hovered/focused/in view
 *   .bedi-reveal        scroll reveal   — start state; JS adds .is-inview
 *   .bedi-press         press feedback  — scale on :active
 *   .bedi-lift          hover feedback  — rise on :hover
 *   .bedi-scroll-locked on <html>       — background scroll lock for drawers
 *
 * Colours come from base.css. There is not a single literal colour in this
 * file, and there must never be one.
 *
 * MOTION CONTRACT
 *   - transform and opacity only. Never width, height, top or left: those
 *     relayout the page every frame and drop the drawer below 60fps.
 *   - 120ms micro (hover, press), 220ms standard (reveal), 420ms large
 *     (drawers and sheets).
 *   - cubic-bezier(.22,.61,.36,1) for entrances, cubic-bezier(.4,0,.2,1) for
 *     exits. Entrances decelerate into place; exits leave evenly.
 *   - Every animation below is neutralised by the one
 *     prefers-reduced-motion block at the end of this file.
 *
 * @package Bedi\Theme
 */

/* =========================================================================
 * 1. MOTION TOKENS
 * ====================================================================== */

:root {
	--bedi-dur-micro: 120ms;
	--bedi-dur: 220ms;
	--bedi-dur-lg: 420ms;

	/* Entrances decelerate; exits are near-linear so a closing drawer does
	   not appear to hesitate halfway. */
	--bedi-ease-in: cubic-bezier(0.22, 0.61, 0.36, 1);
	--bedi-ease-out: cubic-bezier(0.4, 0, 0.2, 1);

	/* How long the glow takes to travel once around the border. Slow on
	   purpose: a fast sweep reads as a loading spinner. */
	--bedi-glow-speed: 6s;

	/* Thickness of the glow ring. Matching --bedi-border-width keeps it
	   sitting exactly on a glass panel's hairline instead of beside it. */
	--bedi-glow-width: 1px;

	/* Distance a .bedi-reveal element travels on its way in. Small by
	   design — a long slide is what makes scroll animation feel cheap. */
	--bedi-reveal-shift: 14px;

	/* How far above a scroll target the sticky header sits. Derived from
	   --bedi-header-height (base.css) so redefining the header height for a
	   breakpoint automatically corrects every in-page anchor. */
	--bedi-scroll-offset: calc(var(--bedi-header-height) + var(--bedi-space-4));
}

/* =========================================================================
 * 2. SMOOTH SCROLL
 *
 * scroll-padding-top stops an anchor target from landing underneath the
 * sticky header — the single most common sticky-header bug. It is not
 * decoration; without it "jump to section" links appear to do nothing.
 * ====================================================================== */

html {
	scroll-behavior: smooth;
	scroll-padding-top: var(--bedi-scroll-offset);

	/*
	 * Reserve the scrollbar's width permanently. When a drawer locks the
	 * background with `overflow: hidden` (section 7) the scrollbar would
	 * otherwise vanish and shift the entire page sideways by ~15px — a jolt
	 * behind every cart open. Reserving it up front means the lock costs
	 * nothing and needs no JavaScript-measured padding compensation.
	 */
	scrollbar-gutter: stable;
}

/* =========================================================================
 * 3. .bedi-glass — the frosted panel
 *
 * The one glass recipe. Header bar, search overlay, cart drawer, category
 * sheet and hero cards are all this class plus their own geometry.
 *
 * backdrop-filter is the expensive part, so it is declared once here and not
 * stacked: nesting a .bedi-glass inside another .bedi-glass blurs the already
 * blurred result and turns the inner panel to mud. Use .bedi-glass on the
 * panel, plain --bedi-glass-2 fills on anything inside it.
 * ====================================================================== */

.bedi-glass {
	background: var(--bedi-glass);
	-webkit-backdrop-filter: blur(18px) saturate(140%);
	backdrop-filter: blur(18px) saturate(140%);
	border: var(--bedi-border-width) solid var(--bedi-hairline);
	box-shadow:
		0 1px 0 0 var(--bedi-glass-sheen) inset,
		var(--bedi-glass-shadow);
}

/*
 * Firefox before 103, and any browser with backdrop-filter disabled, render
 * --bedi-glass as a 5% white wash over the raw page: unreadable. Fall back to
 * the opaque raised surface. This is a correctness fallback, not a nicety —
 * without it the cart drawer is transparent.
 */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
	.bedi-glass {
		background: var(--bedi-surface);
	}
}

/*
 * Add to a .bedi-glass that is also a control (a card, a button, a row). The
 * fill lifts to --bedi-glass-2 and the hairline brightens.
 */
.bedi-glass--interactive {
	transition:
		background-color var(--bedi-dur-micro) var(--bedi-ease-out),
		border-color var(--bedi-dur-micro) var(--bedi-ease-out),
		box-shadow var(--bedi-dur-micro) var(--bedi-ease-out);
}

.bedi-glass--interactive:hover,
.bedi-glass--interactive:focus-within {
	background: var(--bedi-glass-2);
	border-color: var(--bedi-rule-strong);
}

/* =========================================================================
 * 4. .bedi-glow — the animated neon border
 *
 * A conic gradient spinning behind the element, masked down to the 1px border
 * ring so only the edge lights up. IDLE BY DEFAULT: the gradient is
 * transparent and the animation is not running until the element is hovered,
 * contains focus, or is marked .is-inview by the scroll observer. Running a
 * paint-heavy conic gradient on every card at once is how a page ends up at
 * 12fps on a mid-range Android.
 *
 * Put .bedi-glow on the element whose border should light up. It works with
 * or without .bedi-glass.
 *
 * DEGRADATION, in the order it happens:
 *
 *   1. No @property support (Firefox before 128): --bedi-angle cannot be
 *      interpolated, so the @keyframes below has nothing to animate and the
 *      browser simply paints the gradient at its declared 0turn. The border
 *      gets a STATIC accent-to-accent-2 sweep on hover. Correct, just still.
 *      No feature query is needed for this and none is written: the failure
 *      mode is already the desired fallback.
 *   2. No mask-composite: the ring cannot be cut out of the gradient, and an
 *      unmasked ::before would cover the whole element in a coloured slab.
 *      That is a real regression, so the entire ::before lives inside the
 *      @supports gate below and simply does not exist without it.
 *   3. No conic-gradient: replaced with a linear sweep, section 4c.
 * ====================================================================== */

@property --bedi-angle {
	syntax: "<angle>";
	initial-value: 0turn;
	inherits: false;
}

@keyframes bedi-glow-spin {
	to {
		--bedi-angle: 1turn;
	}
}

.bedi-glow {
	position: relative;
}

/* 4a. The ring itself — only where it can actually be masked. */
@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
	.bedi-glow::before {
		content: "";
		position: absolute;

		/*
		 * An absolutely positioned child is laid out against the PADDING box,
		 * so inset: 0 would draw the ring just inside the element's own
		 * border. Pulling it out by one border-width puts it exactly on the
		 * border box, on top of the hairline it replaces.
		 */
		inset: calc(var(--bedi-border-width) * -1);
		border-radius: inherit;
		padding: var(--bedi-glow-width);
		--bedi-angle: 0turn;
		background: conic-gradient(
			from var(--bedi-angle),
			transparent 0turn,
			var(--bedi-accent) 0.12turn,
			var(--bedi-accent-2) 0.25turn,
			transparent 0.4turn
		);

		/*
		 * Two masks: one clipped to the content box, one covering everything.
		 * Excluding the first from the second leaves precisely the padding
		 * ring — a border-width band around the edge — and the gradient shows
		 * only there.
		 *
		 * The #000 here is the one apparent exception to "no literal colours
		 * outside base.css", and it is not really one: a mask reads only the
		 * alpha channel, so this is an opaque stencil, not a colour. Do not
		 * "fix" it into a token — swapping in a translucent brand colour would
		 * make the whole ring semi-transparent.
		 */
		-webkit-mask:
			linear-gradient(#000 0 0) content-box,
			linear-gradient(#000 0 0);
		mask:
			linear-gradient(#000 0 0) content-box,
			linear-gradient(#000 0 0);
		-webkit-mask-composite: xor;
		mask-composite: exclude;

		opacity: 0;
		transition: opacity var(--bedi-dur) var(--bedi-ease-in);
		pointer-events: none;
	}

	/* 4b. Activation. Idle costs nothing; these three states pay for it. */
	.bedi-glow:hover::before,
	.bedi-glow:focus-within::before,
	.bedi-glow.is-inview::before {
		opacity: 1;
		animation: bedi-glow-spin var(--bedi-glow-speed) linear infinite;
	}

	/* 4c. No conic-gradient: a static diagonal sweep in the same two hues. */
	@supports not (background: conic-gradient(from 0turn, red 0turn, blue 1turn)) {
		.bedi-glow::before {
			background: linear-gradient(120deg, var(--bedi-accent), var(--bedi-accent-2));
		}
	}
}

/*
 * Without mask-composite there is no ring, so the element would light up in no
 * way at all. Give it the one thing that always works: the border it already
 * has, recoloured. Same states, same intent, no ::before involved.
 */
@supports not ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
	.bedi-glow {
		transition: border-color var(--bedi-dur) var(--bedi-ease-in);
	}

	.bedi-glow:hover,
	.bedi-glow:focus-within,
	.bedi-glow.is-inview {
		border-color: var(--bedi-accent);
	}
}

/* =========================================================================
 * 5. .bedi-reveal — scroll reveal
 *
 * Start state for anything that should fade up as it enters the viewport. The
 * scroll observer in motion.js adds .is-inview, which clears it.
 *
 * SCOPED TO html.bedi-motion ON PURPOSE, AND TO NOTHING ELSE.
 *
 * motion.js adds .bedi-motion to <html> only once it has an IntersectionObserver
 * in hand that will definitely fire — and removes it again on teardown. It is
 * the only class in the theme that means "the reveals WILL be completed", so it
 * is the only safe thing to hide content behind.
 *
 * This was previously scoped to .bedi-js, and that was a content-loss bug, not a
 * style choice. .bedi-js is written by nav.js AND by header.js, neither of which
 * can reveal anything. If motion.js alone 404s after a bad FTP upload, or trips
 * a parse error, .bedi-js is still set by the other two, .is-inview is never
 * added, and the hero sits at opacity: 0 on a live shop with no way to read it.
 * The whole point of the start state being conditional is defeated by hanging it
 * on a condition some other file satisfies.
 *
 * Never write a bare `.bedi-reveal { opacity: 0 }`, and never re-scope this to
 * .bedi-js: that is the bug this scoping exists to prevent.
 * ====================================================================== */

/*
 * LONGHANDS, AND NO transition-delay. Deliberate, and the reason is specificity.
 *
 * The `transition` shorthand resets transition-delay to 0s along with
 * everything else. This selector is (0,2,1), so that reset outranked every
 * component file trying to sequence its own reveals - hero.css asks for 90ms on
 * the category rail at (0,1,0) and silently got 0ms, which is why the hero
 * arrived as one slab. Declaring the three longhands this module actually owns
 * and leaving transition-delay alone makes the delay a genuine extension point:
 * a component sets it directly, or a stagger container sets it from --bedi-i
 * below, and neither has to fight a shorthand it cannot see.
 *
 * Do not collapse these back into the shorthand.
 */
html.bedi-motion .bedi-reveal {
	opacity: 0;
	transform: translateY(var(--bedi-reveal-shift));
	transition-property: opacity, transform;
	transition-duration: var(--bedi-dur);
	transition-timing-function: var(--bedi-ease-in);
}

html.bedi-motion .bedi-reveal.is-inview {
	opacity: 1;
	transform: none;
}

/*
 * Stagger. Children of a stagger container arrive one after another, 60ms
 * apart, so a row of cards assembles instead of landing as one slab. Mark the
 * container with either data-bedi-stagger or .bedi-stagger; motion.js accepts
 * both and numbers the .bedi-reveal descendants it owns.
 *
 * DRIVEN BY --bedi-i, WHICH motion.js WRITES. This was seven :nth-child()
 * rules, and they were wrong twice over. First, :nth-child counts every
 * sibling, so a single <h2> ahead of the cards shifted the entire sequence, and
 * a grid that wraps each cell got no stagger at all. Second, and fatally,
 * motion.js publishes its sequence as a --bedi-i custom property and nothing
 * read it: the JavaScript half of this feature was inert.
 *
 * The `, 0` fallback is load-bearing, not defensive tidiness. Every
 * .bedi-reveal outside a stagger container has no --bedi-i, and an unresolved
 * var() makes the whole calc() invalid at computed-value time.
 *
 * The ceiling lives in motion.js (12 by default, or the integer value of
 * data-bedi-stagger) because only the JavaScript knows how many items there
 * are: a 40-item category list must not end on a 2.4s delay.
 *
 * Scoped under html.bedi-motion because with no engine there is no reveal to
 * delay, and because a container that explicitly asks to be sequenced should
 * outrank a component's own one-off delay: this is (0,3,1) against hero.css's
 * (0,1,0). The start state above no longer sets transition-delay at all, so
 * this is the only rule in the module that touches it.
 */
html.bedi-motion [data-bedi-stagger] .bedi-reveal,
html.bedi-motion .bedi-stagger .bedi-reveal {
	transition-delay: calc(var(--bedi-i, 0) * 60ms);
}

/* =========================================================================
 * 6. .bedi-press and .bedi-lift — pointer feedback
 *
 * Put .bedi-press on every button, and .bedi-lift on every card or tile that
 * is itself a link. Both are transform-only and compose with each other and
 * with .bedi-glow.
 *
 * .bedi-lift is gated behind (hover: hover) so a touch device does not leave a
 * card stuck in its raised state after a tap — the classic sticky-hover bug.
 * ====================================================================== */

.bedi-press {
	transition: transform var(--bedi-dur-micro) var(--bedi-ease-out);
}

.bedi-press:active {
	transform: scale(0.97);
}

.bedi-lift {
	transition: transform var(--bedi-dur) var(--bedi-ease-in);
}

@media (hover: hover) {
	.bedi-lift:hover {
		transform: translateY(-2px);
	}
}

/* =========================================================================
 * 7. .bedi-scroll-locked — background scroll lock
 *
 * Every dialog in this theme (search overlay, cart drawer, category sheet)
 * locks the page behind it. One class, set on <html> by whichever module
 * opened the dialog, so two overlapping dialogs cannot fight over the lock.
 *
 * overflow: hidden alone would shift the page sideways when the scrollbar
 * disappears; the scrollbar-gutter in section 2 is the other half of this fix
 * and the two must stay together.
 *
 * overscroll-behavior stops a scroll gesture that reaches the end of the
 * drawer's own list from chaining through to the page underneath.
 * ====================================================================== */

html.bedi-scroll-locked,
html.bedi-scroll-locked body {
	overflow: hidden;
}

html.bedi-scroll-locked body {
	overscroll-behavior: none;
}

/* =========================================================================
 * 8. REDUCED MOTION
 *
 * The ONE block that neutralises everything above. Per the WCAG guidance this
 * does not remove the feedback, it removes the movement: transforms go to
 * none, the glow stops travelling but still lights up, and reveals collapse to
 * a near-instant opacity change. Nothing here hides content or removes a focus
 * indicator.
 *
 * base.css carries a global backstop that clamps every duration in the theme
 * to 1ms. This block handles what a duration clamp cannot: the transforms and
 * the infinite animation, which would otherwise still be wrong, just fast.
 * ====================================================================== */

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	/*
	 * Normally unreachable, and kept deliberately. motion.js refuses to arm
	 * under reduced motion, so html.bedi-motion is usually absent whenever this
	 * query matches. The exception is a browser without matchMedia: motion.js
	 * then cannot detect the preference, arms anyway, and this block is the only
	 * thing that honours the visitor's setting. It costs two rules.
	 */
	html.bedi-motion .bedi-reveal {
		transform: none;
		transition: opacity 1ms linear;
	}

	html.bedi-motion [data-bedi-stagger] .bedi-reveal,
	html.bedi-motion .bedi-stagger .bedi-reveal {
		transition-delay: 0ms;
	}

	.bedi-glow:hover::before,
	.bedi-glow:focus-within::before,
	.bedi-glow.is-inview::before {
		/* Still lights up, no longer travels. */
		animation: none;
	}

	.bedi-glass--interactive,
	.bedi-press,
	.bedi-lift {
		transition-duration: 1ms;
	}

	.bedi-press:active,
	.bedi-lift:hover {
		transform: none;
	}
}
