feat: harden portfolio and add SEO foundations

This commit is contained in:
Evandro Fonseca Junior committed 2026-09-04 19:25:30 -03:00
1 parent b079afdd73
commit 62bcfbd8fa
121 files changed
+7769 -10469

No files matched your search

+21 -6
View File
@@ -3,7 +3,7 @@
import dynamic from "next/dynamic";
import type { CSSProperties } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { PortfolioProject } from "@/lib/portfolio-data";
import type { PortfolioProjectView } from "@/lib/portfolio-data";
import NotebookFallback from "./NotebookFallback";
import { selectNotebookProjects } from "./notebookProjects";
import { getNotebookFrame } from "./notebookTimeline";
@@ -18,7 +18,7 @@ const BROWSE_END = 0.78;
type CapabilityWindow = Pick<Window, "innerWidth" | "matchMedia">;
export type NotebookStoryProps = {
projects: PortfolioProject[];
projects: PortfolioProjectView[];
};
export function canUseNotebook3D(view: CapabilityWindow): boolean {
@@ -64,11 +64,17 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
const progressRef = useRef(0);
const activeIndexRef = useRef(notebookProjects.length > 0 ? 0 : -1);
const [engine, setEngine] = useState(false);
const [isVisible, setIsVisible] = useState(true);
const [sectionIntersects, setSectionIntersects] = useState(false);
const [isPageVisible, setIsPageVisible] = useState(
() => typeof document === "undefined" || !document.hidden,
);
const [activeIndex, setActiveIndex] = useState(activeIndexRef.current);
useEffect(() => {
const updateEngine = () => {
setEngine(notebookProjects.length > 0 && canUseNotebook3D(window));
const nextEngine =
notebookProjects.length > 0 && canUseNotebook3D(window);
if (!nextEngine) setSectionIntersects(false);
setEngine(nextEngine);
};
updateEngine();
if (typeof window.matchMedia !== "function") return undefined;
@@ -91,6 +97,15 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
};
}, [notebookProjects.length]);
useEffect(() => {
const handleVisibilityChange = () => {
setIsPageVisible(!document.hidden);
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () =>
document.removeEventListener("visibilitychange", handleVisibilityChange);
}, []);
useEffect(() => {
const section = sectionRef.current;
if (!engine || !section || !("IntersectionObserver" in window)) {
@@ -98,7 +113,7 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
}
const observer = new IntersectionObserver(
([entry]) => setIsVisible(entry?.isIntersecting ?? false),
([entry]) => setSectionIntersects(entry?.isIntersecting ?? false),
{ threshold: 0 },
);
observer.observe(section);
@@ -246,7 +261,7 @@ export default function NotebookStory({ projects }: NotebookStoryProps) {
</div>
<NotebookStage3D
activeIndex={activeIndex}
isVisible={isVisible}
isVisible={sectionIntersects && isPageVisible}
onSelectTab={handleSelectTab}
onStageError={handleStageError}
progress={progressRef}