/**
 * JSD retailer identity
 *
 * The shared behaviour of the retailer indicator, in one place so the compact
 * card, the full card and the single deal page cannot drift apart.
 *
 * There is deliberately no `.amazon`, `.walmart`, `.target` or `.best-buy` rule
 * anywhere in the stylesheets. Every colour arrives as a custom property that
 * JSD_Core_Retailers prints on the retailer element, so adding a retailer is a
 * PHP map entry and nothing else. The fallbacks below are the neutral greys, so
 * an unmapped retailer still renders correctly.
 *
 * Size is left to each component — a 5px dot on a compact tile and a 7px dot on
 * the single page are the same idea at two scales — and only colour and motion
 * live here.
 */

.jsd-retailer-dot {
	display: inline-block;
	flex: 0 0 auto;
	border-radius: 50%;
	background: var(--jsd-retailer-dot, #c9c9c9);
	/*
	 * The dot is the only thing on a JSD card that moves. It exists to say
	 * "this is a live listing at a named store", so it has to be noticeable
	 * once and then forgettable — a slow breath, not a blink.
	 *
	 * The first pass (JSD-14E1R2) was too quiet to register at 100% zoom. The
	 * dot is only 5–7px, so scale alone can never carry the effect: at 1.08 a
	 * 5px dot grows by four tenths of a pixel. The work is done by the other two
	 * channels — a wider opacity swing, and a halo that actually forms before it
	 * fades. Opacity now travels 0.72 → 1, scale 1 → 1.14, and the ring reaches
	 * 3px at 0.22 alpha on its way out to 5px, over a 3s cycle.
	 *
	 * Every animated property — transform, opacity, box-shadow — is composited
	 * or paint-only. None of them is a layout property, so the animation cannot
	 * shift a card by a pixel however large the halo gets.
	 *
	 * The curve is symmetric about 50% and the 0% and 100% stops are identical,
	 * so the loop closes seamlessly: the dot breathes out and back in, and never
	 * snaps between states.
	 */
	will-change: transform;
}

@keyframes jsd-retailer-dot-pulse {
	0% {
		transform: scale(1);
		opacity: 0.72;
		box-shadow: 0 0 0 0 rgba(var(--jsd-retailer-dot-rgb, 201, 201, 201), 0.38);
	}

	30% {
		transform: scale(1.09);
		opacity: 0.93;
		box-shadow: 0 0 0 3px rgba(var(--jsd-retailer-dot-rgb, 201, 201, 201), 0.22);
	}

	50% {
		transform: scale(1.14);
		opacity: 1;
		box-shadow: 0 0 0 5px rgba(var(--jsd-retailer-dot-rgb, 201, 201, 201), 0);
	}

	70% {
		transform: scale(1.09);
		opacity: 0.93;
		box-shadow: 0 0 0 3px rgba(var(--jsd-retailer-dot-rgb, 201, 201, 201), 0.22);
	}

	100% {
		transform: scale(1);
		opacity: 0.72;
		box-shadow: 0 0 0 0 rgba(var(--jsd-retailer-dot-rgb, 201, 201, 201), 0.38);
	}
}

/*
 * Opt in rather than opt out: a browser that reports no preference gets the
 * pulse, and anything that cannot answer the question stays still.
 */
@media (prefers-reduced-motion: no-preference) {
	.jsd-retailer-dot {
		animation: jsd-retailer-dot-pulse 3s ease-in-out infinite;
	}
}

/*
 * And the explicit reduce case, so the intent is stated rather than inferred
 * from the absence of the rule above. The indicator remains static, keeps its
 * retailer colour, and sits at the animation's resting scale and full opacity.
 */
@media (prefers-reduced-motion: reduce) {
	.jsd-retailer-dot {
		animation: none;
		transform: none;
		opacity: 1;
		box-shadow: none;
		will-change: auto;
	}
}
