/**
 * MCN Animations — Click & state
 *
 * Rebuilt from:
 *   Jon Kantner,       "Toggle Switch with a Hole Handle" (codepen.io/jkantner/pen/OJNezxj, MIT)
 *   Jhey,              "Pure CSS Drawers"                 (codepen.io/jh3y/pen/ExNQxKV, MIT)
 *   Arjun Ace,         "Copy Button Click Effect"         (codepen.io/arjunace/pen/dyMdbyr, MIT)
 *   Brandon McConnell, "SCSS Beveled Buttons"             (codepen.io/brandonmcconnell/pen/bzwMxx, MIT)
 *   Mikael Ainalem,    "Black Biometrics Button"          (codepen.io/ainalem/pen/mQBNpg, MIT)
 *
 * THE BIG CHANGE FROM THE ORIGINALS
 * Every one of these pens drives state from a hidden checkbox. That is fine
 * for a demo and wrong for a site: a checkbox announces as "checkbox" even
 * when it is really a switch, a disclosure, or a submit button. Here, state
 * lives on a real button and is expressed through ARIA:
 *
 *   toggle     -> <button role="switch" aria-checked="true|false">
 *   drawer     -> <button aria-expanded="true|false" aria-controls="id">
 *   copy       -> <button> + a visually-hidden aria-live="polite" region
 *   biometric  -> <button aria-busy="true"> while scanning
 *
 * CSS keys off [aria-checked], [aria-expanded] and [aria-busy] directly, so
 * the styling and the accessibility tree can never drift apart. If someone
 * forgets the ARIA attribute, the effect visibly does not work — which is the
 * failure mode you want.
 *
 * @package MCN_Animations
 */

/* --------------------------------------------------------------------------
 * Toggle switch
 * ----------------------------------------------------------------------- */

.anim-switch {
	--switch-w: 56px;
	--switch-h: 30px;
	--switch-pad: 3px;

	position: relative;
	display: inline-flex;
	align-items: center;
	width: var(--switch-w);
	height: var(--switch-h);
	/* 2.5.8 (AA) — 30px tall clears the 24px minimum target. */
	min-height: 24px;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: var(--anim-switch-off, #8b8fa3);
	cursor: pointer;
	transition: background-color var(--anim-duration) var(--anim-ease);
}

.anim-switch__handle {
	position: absolute;
	top: var(--switch-pad);
	left: var(--switch-pad);
	width: calc(var(--switch-h) - var(--switch-pad) * 2);
	height: calc(var(--switch-h) - var(--switch-pad) * 2);
	border-radius: 50%;
	background: #fff;
	transition:
		transform var(--anim-duration) var(--anim-ease-overshoot),
		box-shadow var(--anim-duration) var(--anim-ease);
	/* The "hole" — an inset ring that opens up as the switch turns on. */
	box-shadow: inset 0 0 0 var(--switch-hole, 0px) var(--anim-switch-off, #8b8fa3);
}

.anim-switch[aria-checked="true"] {
	background: var(--anim-switch-on, #3eb489);
}

.anim-switch[aria-checked="true"] .anim-switch__handle {
	transform: translateX(calc(var(--switch-w) - var(--switch-h)));
	--switch-hole: 6px;
	box-shadow: inset 0 0 0 6px var(--anim-switch-on, #3eb489);
}

/* State must never be conveyed by colour alone (1.4.1, Level A). The handle
   position is the second channel, and it is a shape change, not a hue change. */

/* --------------------------------------------------------------------------
 * Drawer / disclosure
 *
 * Uses grid-template-rows 0fr -> 1fr, which animates to the content's real
 * height with no JS measuring and no max-height guessing.
 * ----------------------------------------------------------------------- */

.anim-drawer__panel {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows var(--anim-duration) var(--anim-ease);
}

.anim-drawer__panel > * {
	overflow: hidden;
	min-height: 0;
	/**
	 * Vertical padding on the direct child cannot shrink with the row, so a
	 * closed drawer keeps a sliver of height equal to that padding. Zero it
	 * here and put spacing on .anim-drawer__content instead — that nests one
	 * level deeper, where it is free to be clipped.
	 */
	padding-block: 0;
}

.anim-drawer__content {
	padding: var(--anim-drawer-pad, 0.75rem 0 0.25rem);
}

.anim-drawer__trigger[aria-expanded="true"] + .anim-drawer__panel,
.anim-drawer__panel[data-open="true"] {
	grid-template-rows: 1fr;
}

.anim-drawer__icon {
	display: inline-block;
	transition: transform var(--anim-duration) var(--anim-ease);
}

.anim-drawer__trigger[aria-expanded="true"] .anim-drawer__icon {
	transform: rotate(180deg);
}

/* 3D lid variant — the panel tips open like a real drawer front. */
.anim-drawer--3d {
	perspective: 900px;
}

.anim-drawer--3d .anim-drawer__panel > * {
	transform-origin: top center;
	transform: rotateX(-92deg);
	opacity: 0;
	transition:
		transform var(--anim-duration-slow) var(--anim-ease),
		opacity var(--anim-duration) var(--anim-ease);
}

.anim-drawer--3d .anim-drawer__trigger[aria-expanded="true"] + .anim-drawer__panel > * {
	transform: none;
	opacity: 1;
}

/* --------------------------------------------------------------------------
 * Copy button
 *
 * Press dip, then the icon swaps to a tick. The announcement is carried by a
 * live region, not by the animation — a screen reader user gets "Copied"
 * even though they never see the tick.
 * ----------------------------------------------------------------------- */

.anim-copy {
	position: relative;
	transition: transform 90ms var(--anim-ease);
}

.anim-copy:active {
	transform: scale(0.94);
}

.anim-copy__icon,
.anim-copy__tick {
	display: inline-grid;
	place-items: center;
	transition:
		transform var(--anim-duration) var(--anim-ease-overshoot),
		opacity var(--anim-duration-fast) var(--anim-ease);
}

.anim-copy__tick {
	position: absolute;
	inset: 0;
	transform: scale(0.4);
	opacity: 0;
	color: var(--anim-switch-on, #3eb489);
}

.anim-copy.is-copied .anim-copy__icon {
	transform: scale(0.4);
	opacity: 0;
}

.anim-copy.is-copied .anim-copy__tick {
	transform: none;
	opacity: 1;
}

/* --------------------------------------------------------------------------
 * Bevel press
 * ----------------------------------------------------------------------- */

.anim-bevel {
	--bevel: 4px;
	border: 0;
	border-radius: 8px;
	background: var(--anim-btn-face, #2c3940);
	color: #fff;
	box-shadow:
		inset 0 var(--bevel) 0 rgb(255 255 255 / 0.18),
		inset 0 calc(var(--bevel) * -1) 0 rgb(0 0 0 / 0.28),
		0 var(--bevel) 0 var(--anim-btn-shade, #16202536);
	transition:
		transform var(--anim-duration-fast) var(--anim-ease),
		box-shadow var(--anim-duration-fast) var(--anim-ease);
}

.anim-bevel:active,
.anim-bevel[aria-pressed="true"] {
	transform: translateY(var(--bevel));
	box-shadow:
		inset 0 calc(var(--bevel) * -1) 0 rgb(255 255 255 / 0.12),
		inset 0 var(--bevel) 0 rgb(0 0 0 / 0.3),
		0 0 0 var(--anim-btn-shade, #162025);
}

/* --------------------------------------------------------------------------
 * Biometric / scan button
 *
 * stroke-dasharray + stroke-dashoffset draw the fingerprint paths in
 * sequence. Driven by [aria-busy], so the scan state is real to AT.
 * ----------------------------------------------------------------------- */

.anim-scan__paths path {
	fill: none;
	stroke: var(--anim-accent);
	stroke-width: 2;
	stroke-linecap: round;
	stroke-dasharray: var(--len, 120);
	stroke-dashoffset: var(--len, 120);
}

.anim-scan[aria-busy="true"] .anim-scan__paths path {
	animation: anim-scan-draw 1.6s var(--anim-ease) forwards;
	animation-delay: calc(var(--anim-index, 0) * 140ms);
}

.anim-scan__label {
	transition: opacity var(--anim-duration) var(--anim-ease);
}

.anim-scan[aria-busy="true"] .anim-scan__label {
	opacity: 0;
}

@keyframes anim-scan-draw {
	to { stroke-dashoffset: 0; }
}

@media (prefers-reduced-motion: reduce) {

	.anim-switch,
	.anim-switch__handle,
	.anim-drawer__panel,
	.anim-drawer--3d .anim-drawer__panel > *,
	.anim-drawer__icon,
	.anim-copy,
	.anim-copy__icon,
	.anim-copy__tick,
	.anim-bevel {
		transition: none !important;
	}

	.anim-drawer--3d .anim-drawer__panel > * {
		transform: none;
		opacity: 1;
	}

	.anim-scan[aria-busy="true"] .anim-scan__paths path {
		animation: none;
		stroke-dashoffset: 0;
	}
}
