refactor(home): remove a secao animada do notebook
O mergulho do hero termina direto em Projetos. As capturas de tela seguem nos cards (ProjectCover). Remove componentes, testes e 670 linhas de CSS do notebook. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
92c602c0ad
commit
44efcfcb20
18 files changed
+30
-3059
No files matched your search
@@ -9,7 +9,7 @@ function HomeNavigation() {
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={onLayoutReady}>
|
||||
Establish notebook layout
|
||||
Establish layout
|
||||
</button>
|
||||
<a href="/#about">Sobre</a>
|
||||
<a href="/#contact">Contato</a>
|
||||
@@ -42,7 +42,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("home fragment navigation", () => {
|
||||
it("starts an unfragmented visit at the top after the notebook establishes layout", () => {
|
||||
it("starts an unfragmented visit at the top after the home establishes layout", () => {
|
||||
let restoredPosition = 3200;
|
||||
jest
|
||||
.spyOn(window, "scrollTo")
|
||||
@@ -52,7 +52,7 @@ describe("home fragment navigation", () => {
|
||||
});
|
||||
render(<HomeNavigation />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(restoredPosition).toBe(0);
|
||||
});
|
||||
@@ -63,7 +63,7 @@ describe("home fragment navigation", () => {
|
||||
.mockImplementation(() => {});
|
||||
const firstVisit = render(<HomeNavigation />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
firstVisit.unmount();
|
||||
expect(window.history.scrollRestoration).not.toBe("manual");
|
||||
@@ -71,7 +71,7 @@ describe("home fragment navigation", () => {
|
||||
scrollTo.mockClear();
|
||||
render(<HomeNavigation />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(scrollTo).not.toHaveBeenCalled();
|
||||
expect(window.history.scrollRestoration).not.toBe("manual");
|
||||
@@ -88,24 +88,24 @@ describe("home fragment navigation", () => {
|
||||
position = 600;
|
||||
fireEvent.wheel(window, { deltaY: 600 });
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(position).toBe(600);
|
||||
});
|
||||
|
||||
it("waits for the notebook layout on fragment arrival, then never steals focus on refresh", () => {
|
||||
it("waits for the home layout on fragment arrival, then never steals focus on refresh", () => {
|
||||
window.history.replaceState(null, "", "/#contact");
|
||||
render(<HomeNavigation />);
|
||||
const contact = screen.getByRole("region", { name: "Contato" });
|
||||
expect(contact).not.toHaveFocus();
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(contact).toHaveFocus();
|
||||
screen.getByRole("button", { name: "Read biography" }).focus();
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Read biography" }),
|
||||
@@ -119,7 +119,7 @@ describe("home fragment navigation", () => {
|
||||
message.focus();
|
||||
fireEvent.wheel(window, { deltaY: 200 });
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(message).toHaveFocus();
|
||||
});
|
||||
@@ -128,7 +128,7 @@ describe("home fragment navigation", () => {
|
||||
window.history.replaceState(null, "", "/#about");
|
||||
render(<HomeNavigation />);
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
const link = screen.getByRole("link", { name: "Sobre" });
|
||||
link.focus();
|
||||
@@ -152,7 +152,7 @@ describe("home fragment navigation", () => {
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
});
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Establish notebook layout" }),
|
||||
screen.getByRole("button", { name: "Establish layout" }),
|
||||
);
|
||||
expect(message).toHaveFocus();
|
||||
});
|
||||
|
||||
@@ -14,9 +14,8 @@ import Hero, { type HeroStats } from "../Hero";
|
||||
import LighthouseMetrics from "../LighthouseMetrics";
|
||||
import Projects from "../Projects";
|
||||
import Skills from "../Skills";
|
||||
import NotebookStory from "../story/NotebookStory";
|
||||
import { WorkModalities } from "../WorkModalities";
|
||||
import { useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import ExperienceDisclosure from "./ExperienceDisclosure";
|
||||
import { useCinematicScroll } from "./useCinematicScroll";
|
||||
import { useHomeFragmentNavigation } from "./useHomeFragmentNavigation";
|
||||
@@ -35,6 +34,8 @@ export default function CinematicHome({
|
||||
const onLayoutReady = useHomeFragmentNavigation();
|
||||
const mainRef = useRef<HTMLElement>(null);
|
||||
useCinematicScroll(mainRef);
|
||||
// o layout da home é estável na montagem: a chegada por fragmento (#contato etc.) já pode ser resolvida
|
||||
useEffect(() => onLayoutReady(), [onLayoutReady]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col overflow-x-clip">
|
||||
@@ -42,8 +43,6 @@ export default function CinematicHome({
|
||||
<main ref={mainRef} className="ds-main relative z-0">
|
||||
<Hero stats={heroStats} />
|
||||
|
||||
<NotebookStory projects={projects} onLayoutReady={onLayoutReady} />
|
||||
|
||||
<Projects projects={projects} />
|
||||
|
||||
<WorkModalities />
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function HeroField() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [failed, setFailed] = useState(false);
|
||||
// fim do mergulho: a cena já está no escuro e o hero se dissolve; parar o desenho poupa a GPU para o notebook
|
||||
// fim do mergulho: a cena já está no escuro e o hero se dissolve; parar o desenho poupa a GPU
|
||||
const [done, setDone] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,35 +36,20 @@ const holdPrev = (tl: gsap.core.Timeline, prev: HTMLElement) =>
|
||||
// a seção que entra também fica parada no topo: a transição acontece "no lugar"
|
||||
const holdNext = (tl: gsap.core.Timeline, next: HTMLElement) =>
|
||||
tl.fromTo(next, { y: () => -vh() }, { y: 0 }, 0);
|
||||
const stageOf = (section: HTMLElement) =>
|
||||
section.querySelector<HTMLElement>(".notebook-story-sticky") ?? section;
|
||||
|
||||
// uma linguagem diferente por passagem, na ordem das seções da home
|
||||
const TRANSITIONS: Transition[] = [
|
||||
// hero → notebook: o mergulho termina no escuro, o hero some e o notebook abre como tela de cinema
|
||||
// hero → projetos: o mergulho termina no escuro, o hero some e os projetos abrem como tela de cinema
|
||||
(tl, prev, next) => {
|
||||
holdPrev(tl, prev);
|
||||
holdNext(tl, next);
|
||||
const band = (open: number) => () =>
|
||||
`inset(${(vh() * (1 - open)) / 2}px 0px ${next.offsetHeight - vh() + (vh() * (1 - open)) / 2}px 0px)`;
|
||||
tl.fromTo(prev, { autoAlpha: 1 }, { autoAlpha: 0, duration: 0.4 }, 0).fromTo(
|
||||
stageOf(next),
|
||||
{ opacity: 0, clipPath: "inset(32% 0% 32% 0%)" },
|
||||
{ opacity: 1, clipPath: "inset(0% 0% 0% 0%)", duration: 0.65 },
|
||||
0.35,
|
||||
);
|
||||
},
|
||||
// notebook → projetos: a seção sobe como um cartão e o notebook recolhe ao fundo
|
||||
(tl, prev, next) => {
|
||||
holdPrev(tl, prev);
|
||||
tl.fromTo(
|
||||
stageOf(prev),
|
||||
{ opacity: 1, clipPath: "inset(0% 0% 0% 0% round 0px)" },
|
||||
{ opacity: 0.15, clipPath: "inset(8% 6% 8% 6% round 32px)" },
|
||||
0,
|
||||
).fromTo(
|
||||
next,
|
||||
{ clipPath: "inset(0% 5% 0% 5% round 40px 40px 0px 0px)" },
|
||||
{ clipPath: "inset(0% 0% 0% 0% round 0px 0px 0px 0px)" },
|
||||
0,
|
||||
{ opacity: 0, clipPath: band(0.36) },
|
||||
{ opacity: 1, clipPath: band(1), duration: 0.65 },
|
||||
0.35,
|
||||
);
|
||||
},
|
||||
// projetos → modalidades: uma íris abre do centro enquanto a anterior avança contra a câmera
|
||||
@@ -197,7 +182,7 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
||||
const scenes = Array.from(main.children) as HTMLElement[];
|
||||
const context = gsap.context(() => {
|
||||
scenes.forEach((scene, index) => {
|
||||
// quem entra fica por cima; o hero, que sai por dissolução, fica por cima do notebook
|
||||
// quem entra fica por cima; o hero, que sai por dissolução, fica por cima da cena seguinte
|
||||
gsap.set(scene, { position: "relative", zIndex: index === 0 ? 30 : index + 1 });
|
||||
});
|
||||
TRANSITIONS.forEach((transition, index) => {
|
||||
|
||||
Reference in new issue
Block a user