86 lines
2.3 KiB
Svelte
86 lines
2.3 KiB
Svelte
<script lang="ts">
|
|
import { prefersReducedMotion, Spring } from 'svelte/motion';
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
class?: string;
|
|
stroke?: number;
|
|
}
|
|
|
|
let { open, class: className = '', stroke = 2 }: Props = $props();
|
|
|
|
const progress = Spring.of(() => (open ? 1 : 0), {
|
|
stiffness: 0.25,
|
|
damping: 0.65,
|
|
precision: 0.001
|
|
});
|
|
|
|
const clamp01 = (n: number) => Math.max(0, Math.min(1, n));
|
|
const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
|
|
|
|
let t = $derived(prefersReducedMotion.current ? (open ? 1 : 0) : progress.current);
|
|
let tFast = $derived(clamp01(t * 1.15));
|
|
|
|
// Fechado: hambúrguer. Aberto: "outro menu" (linhas deslocadas + comprimentos diferentes).
|
|
// Continua sendo ícone de menu (não vira X).
|
|
let topY = $derived(lerp(-6, -7, tFast));
|
|
let botY = $derived(lerp(6, 7, tFast));
|
|
|
|
let topX = $derived(lerp(0, 3.25, t));
|
|
let midX = $derived(lerp(0, -2.75, t));
|
|
let botX = $derived(lerp(0, 1.75, t));
|
|
|
|
// micro-inclinação só pra dar “vida”, sem cruzar em X
|
|
let topR = $derived(lerp(0, 2.5, tFast));
|
|
let botR = $derived(lerp(0, -2.5, tFast));
|
|
|
|
let topScaleX = $derived(lerp(1, 0.62, tFast));
|
|
let midScaleX = $derived(lerp(1, 0.92, tFast));
|
|
let botScaleX = $derived(lerp(1, 0.72, tFast));
|
|
|
|
let topOpacity = $derived(1);
|
|
let midOpacity = $derived(1);
|
|
let botOpacity = $derived(1);
|
|
</script>
|
|
|
|
<span class="menu-toggle-icon {className}" aria-hidden="true" style="--stroke: {stroke}px">
|
|
<span
|
|
class="line"
|
|
style="--x: {topX}px; --y: {topY}px; --r: {topR}deg; --o: {topOpacity}; --sx: {topScaleX}"
|
|
></span>
|
|
<span
|
|
class="line"
|
|
style="--x: {midX}px; --y: 0px; --r: 0deg; --o: {midOpacity}; --sx: {midScaleX}"
|
|
></span>
|
|
<span
|
|
class="line"
|
|
style="--x: {botX}px; --y: {botY}px; --r: {botR}deg; --o: {botOpacity}; --sx: {botScaleX}"
|
|
></span>
|
|
</span>
|
|
|
|
<style>
|
|
.menu-toggle-icon {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
color: currentColor;
|
|
}
|
|
|
|
.line {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 50%;
|
|
margin-top: calc(var(--stroke) / -2);
|
|
height: var(--stroke);
|
|
border-radius: 9999px;
|
|
background: currentColor;
|
|
opacity: var(--o, 1);
|
|
transform-origin: center;
|
|
transform: translateX(var(--x, 0px)) translateY(var(--y, 0px)) rotate(var(--r, 0deg))
|
|
scaleX(var(--sx, 1));
|
|
will-change: transform, opacity;
|
|
}
|
|
</style>
|