fix: stabilize reveals and update contact email
This commit is contained in:
1 parent
aad56ad885
commit
cdde81eb6e
22 files changed
+144
-40
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useReducedMotion, type Variants } from "framer-motion";
|
import { motion, type Variants } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import profilePhoto from "@/public/edev.jpg";
|
import profilePhoto from "@/public/edev.jpg";
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
import { LazyRevealImage } from "./animations/LazyRevealImage";
|
import { LazyRevealImage } from "./animations/LazyRevealImage";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import { buildWhatsAppUrl, getWhatsAppDisplayNumber } from "@/lib/whatsapp";
|
import { buildWhatsAppUrl, getWhatsAppDisplayNumber } from "@/lib/whatsapp";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
|
||||||
import { Section } from "./Section";
|
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
import MagneticButton from "./cinematic/MagneticButton";
|
import MagneticButton from "./cinematic/MagneticButton";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
export default function Contact() {
|
export default function Contact() {
|
||||||
const shouldReduceMotion = useReducedMotion();
|
const shouldReduceMotion = useReducedMotion();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import type { PortfolioExperience } from "@/lib/portfolio-data";
|
import type { PortfolioExperience } from "@/lib/portfolio-data";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
|
||||||
import { Section } from "./Section";
|
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
export default function Experience({
|
export default function Experience({
|
||||||
experiences,
|
experiences,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe("Footer Component - Contact Information", () => {
|
|||||||
it("should render email address", () => {
|
it("should render email address", () => {
|
||||||
render(<Footer />);
|
render(<Footer />);
|
||||||
|
|
||||||
const emailLink = screen.getByText("evandro@dev.br");
|
const emailLink = screen.getByText("evandro[email protected]");
|
||||||
expect(emailLink).toBeInTheDocument();
|
expect(emailLink).toBeInTheDocument();
|
||||||
expect(emailLink.tagName).toBe("A");
|
expect(emailLink.tagName).toBe("A");
|
||||||
});
|
});
|
||||||
@@ -21,14 +21,17 @@ describe("Footer Component - Contact Information", () => {
|
|||||||
it("should have mailto link for email", () => {
|
it("should have mailto link for email", () => {
|
||||||
render(<Footer />);
|
render(<Footer />);
|
||||||
|
|
||||||
const emailLink = screen.getByText("evandro@dev.br");
|
const emailLink = screen.getByText("evandro[email protected]");
|
||||||
expect(emailLink).toHaveAttribute("href", "mailto:[email protected]");
|
expect(emailLink).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"mailto:[email protected]",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should apply correct styling classes to email link", () => {
|
it("should apply correct styling classes to email link", () => {
|
||||||
render(<Footer />);
|
render(<Footer />);
|
||||||
|
|
||||||
const emailLink = screen.getByText("evandro@dev.br");
|
const emailLink = screen.getByText("evandro[email protected]");
|
||||||
const classes = emailLink.className;
|
const classes = emailLink.className;
|
||||||
|
|
||||||
expect(classes).toContain("text-[12px]");
|
expect(classes).toContain("text-[12px]");
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ export default function Footer() {
|
|||||||
</p>
|
</p>
|
||||||
<MagneticButton
|
<MagneticButton
|
||||||
as="a"
|
as="a"
|
||||||
href="mailto:evandro@dev.br"
|
href="mailto:evandro[email protected]"
|
||||||
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
|
className="inline-block text-[18px] font-bold text-[var(--ink)] transition-colors duration-200 hover:text-[var(--slash)]"
|
||||||
strength={0.2}
|
strength={0.2}
|
||||||
>
|
>
|
||||||
evandro@dev.br
|
evandrofonsecajunior@gmail.com
|
||||||
</MagneticButton>
|
</MagneticButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
|
|
||||||
const METRICS = [
|
const METRICS = [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface LogoProps {
|
interface LogoProps {
|
||||||
variant?: "mark" | "wordmark" | "icon";
|
variant?: "mark" | "wordmark" | "icon";
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import type { PortfolioProject } from "@/lib/portfolio-data";
|
import type { PortfolioProject } from "@/lib/portfolio-data";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
|
||||||
import { Section } from "./Section";
|
|
||||||
import MagneticButton from "./cinematic/MagneticButton";
|
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
|
import MagneticButton from "./cinematic/MagneticButton";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
export default function Projects({
|
export default function Projects({
|
||||||
projects,
|
projects,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface RevealProps {
|
interface RevealProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { ReactNode } from "react";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface RouteTransitionProps {
|
interface RouteTransitionProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
import { TextDiffuseReveal } from "./animations/TextDiffuseReveal";
|
import { TextDiffuseReveal } from "./animations/TextDiffuseReveal";
|
||||||
import { Reveal } from "./Reveal";
|
import { Reveal } from "./Reveal";
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import type { PortfolioSkillCategory } from "@/lib/portfolio-data";
|
import type { PortfolioSkillCategory } from "@/lib/portfolio-data";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
|
||||||
import { Section } from "./Section";
|
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
export default function Skills({
|
export default function Skills({
|
||||||
categories,
|
categories,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion, type Variants } from "framer-motion";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
import { serviceHubLinks } from "@/lib/site-content";
|
import { serviceHubLinks } from "@/lib/site-content";
|
||||||
import { Section } from "./Section";
|
|
||||||
import { DrawLine } from "./animations/DrawLine";
|
import { DrawLine } from "./animations/DrawLine";
|
||||||
import MagneticButton from "./cinematic/MagneticButton";
|
import MagneticButton from "./cinematic/MagneticButton";
|
||||||
|
import { Section } from "./Section";
|
||||||
|
|
||||||
interface Tier {
|
interface Tier {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -246,4 +247,3 @@ export function WorkModalities() {
|
|||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useInView } from "framer-motion";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useInView, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface CounterValueProps {
|
interface CounterValueProps {
|
||||||
value: number;
|
value: number;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface DrawLineProps {
|
interface DrawLineProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { motion } from "framer-motion";
|
||||||
import Image, { type StaticImageData } from "next/image";
|
import Image, { type StaticImageData } from "next/image";
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { useState } from "react";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface LazyRevealImageProps {
|
interface LazyRevealImageProps {
|
||||||
src: string | StaticImageData;
|
src: string | StaticImageData;
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { TextDiffuseReveal } from "./TextDiffuseReveal";
|
||||||
|
|
||||||
|
let mockReducedMotion = false;
|
||||||
|
let mockInView = true;
|
||||||
|
let mockAnimations: Array<{ animate?: Record<string, unknown> }> = [];
|
||||||
|
jest.mock("framer-motion", () => {
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
return {
|
||||||
|
motion: {
|
||||||
|
span: ({
|
||||||
|
children,
|
||||||
|
initial: _initial,
|
||||||
|
animate,
|
||||||
|
transition: _transition,
|
||||||
|
onAnimationComplete: _onAnimationComplete,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
initial?: unknown;
|
||||||
|
animate?: Record<string, unknown>;
|
||||||
|
transition?: unknown;
|
||||||
|
onAnimationComplete?: () => void;
|
||||||
|
} & Record<string, unknown>) => {
|
||||||
|
mockAnimations.push({ animate });
|
||||||
|
return React.createElement("span", props, children);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useInView: () => mockInView,
|
||||||
|
useReducedMotion: () => mockReducedMotion,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("TextDiffuseReveal reduced motion", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockReducedMotion = false;
|
||||||
|
mockInView = true;
|
||||||
|
mockAnimations = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears blur and scale when reduced motion resolves after mount", () => {
|
||||||
|
const { rerender } = render(<TextDiffuseReveal text="Reveal" />);
|
||||||
|
|
||||||
|
mockAnimations = [];
|
||||||
|
mockReducedMotion = true;
|
||||||
|
rerender(<TextDiffuseReveal text="Reveal" />);
|
||||||
|
|
||||||
|
expect(mockAnimations).toHaveLength(6);
|
||||||
|
expect(mockAnimations).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
Array.from({ length: 6 }, () =>
|
||||||
|
expect.objectContaining({
|
||||||
|
animate: {
|
||||||
|
opacity: 1,
|
||||||
|
filter: "blur(0px)",
|
||||||
|
scale: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the first render independent of the reduced-motion preference", () => {
|
||||||
|
mockReducedMotion = true;
|
||||||
|
mockInView = false;
|
||||||
|
|
||||||
|
render(<TextDiffuseReveal text="Reveal" />);
|
||||||
|
|
||||||
|
expect(mockAnimations[0]).toEqual({
|
||||||
|
animate: {
|
||||||
|
filter: "blur(12px)",
|
||||||
|
opacity: 0,
|
||||||
|
scale: 0.9,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useInView, useReducedMotion } from "framer-motion";
|
import { motion, useInView } from "framer-motion";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface TextDiffuseRevealProps {
|
interface TextDiffuseRevealProps {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -101,11 +102,9 @@ export function TextDiffuseReveal({
|
|||||||
}
|
}
|
||||||
animate={
|
animate={
|
||||||
isInView
|
isInView
|
||||||
? shouldReduceMotion
|
? { filter: "blur(0px)", opacity: 1, scale: 1 }
|
||||||
? { opacity: 1 }
|
|
||||||
: { filter: "blur(0px)", opacity: 1, scale: 1 }
|
|
||||||
: shouldReduceMotion
|
: shouldReduceMotion
|
||||||
? { opacity: 0 }
|
? { filter: "blur(0px)", opacity: 0, scale: 1 }
|
||||||
: { filter: "blur(12px)", opacity: 0, scale: 0.9 }
|
: { filter: "blur(12px)", opacity: 0, scale: 0.9 }
|
||||||
}
|
}
|
||||||
transition={{
|
transition={{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
interface TopProgressBarProps {
|
interface TopProgressBarProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
const HeroScene = dynamic(() => import("./HeroScene"), { ssr: false });
|
const HeroScene = dynamic(() => import("./HeroScene"), { ssr: false });
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
||||||
|
|
||||||
const EXIT_EASE: [number, number, number, number] = [0.76, 0, 0.24, 1];
|
const EXIT_EASE: [number, number, number, number] = [0.76, 0, 0.24, 1];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useReducedMotion as useFramerReducedMotion } from "framer-motion";
|
import { useReducedMotion as useFramerReducedMotion } from "framer-motion";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
/**
|
/**
|
||||||
* Hook que detecta a preferência prefers-reduced-motion do sistema operacional.
|
* Hook que detecta a preferência prefers-reduced-motion do sistema operacional.
|
||||||
* Wrapper sobre o hook useReducedMotion do Framer Motion.
|
* Wrapper sobre o hook useReducedMotion do Framer Motion.
|
||||||
@@ -20,5 +20,12 @@ import { useReducedMotion as useFramerReducedMotion } from "framer-motion";
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function useReducedMotion(): boolean {
|
export function useReducedMotion(): boolean {
|
||||||
return useFramerReducedMotion() ?? false;
|
const systemPreference = useFramerReducedMotion();
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isMounted && systemPreference === true;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user