feat: Initialize portfolio project with core pages, UI components, and configuration files.

This commit is contained in:
Evandro Fonseca Junior committed 2026-03-07 10:27:04 -03:00
1 parent aa07d6faeb
commit 0523052864
38 files changed
+1165 -460

No files matched your search

+41 -27
View File
@@ -1,7 +1,7 @@
'use client';
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
import { gsap } from 'gsap';
import { InertiaPlugin } from 'gsap/InertiaPlugin';
"use client";
import React, { useRef, useEffect, useCallback, useMemo } from "react";
import { gsap } from "gsap";
import { InertiaPlugin } from "gsap/InertiaPlugin";
gsap.registerPlugin(InertiaPlugin);
@@ -46,15 +46,15 @@ function hexToRgb(hex: string) {
return {
r: parseInt(m[1], 16),
g: parseInt(m[2], 16),
b: parseInt(m[3], 16)
b: parseInt(m[3], 16),
};
}
const DotGrid: React.FC<DotGridProps> = ({
dotSize = 16,
gap = 32,
baseColor = '#5227FF',
activeColor = '#5227FF',
baseColor = "#5227FF",
activeColor = "#5227FF",
proximity = 150,
speedTrigger = 100,
shockRadius = 250,
@@ -62,8 +62,8 @@ const DotGrid: React.FC<DotGridProps> = ({
maxSpeed = 5000,
resistance = 750,
returnDuration = 1.5,
className = '',
style
className = "",
style,
}) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -76,14 +76,14 @@ const DotGrid: React.FC<DotGridProps> = ({
speed: 0,
lastTime: 0,
lastX: 0,
lastY: 0
lastY: 0,
});
const baseRgb = useMemo(() => hexToRgb(baseColor), [baseColor]);
const activeRgb = useMemo(() => hexToRgb(activeColor), [activeColor]);
const circlePath = useMemo(() => {
if (typeof window === 'undefined' || !window.Path2D) return null;
if (typeof window === "undefined" || !window.Path2D) return null;
const p = new Path2D();
p.arc(0, 0, dotSize / 2, 0, Math.PI * 2);
@@ -102,7 +102,7 @@ const DotGrid: React.FC<DotGridProps> = ({
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext("2d");
if (ctx) ctx.scale(dpr, dpr);
const cols = Math.floor((width + gap) / (dotSize + gap));
@@ -138,7 +138,7 @@ const DotGrid: React.FC<DotGridProps> = ({
const draw = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -178,15 +178,15 @@ const DotGrid: React.FC<DotGridProps> = ({
useEffect(() => {
buildGrid();
let ro: ResizeObserver | null = null;
if ('ResizeObserver' in window) {
if ("ResizeObserver" in window) {
ro = new ResizeObserver(buildGrid);
wrapperRef.current && ro.observe(wrapperRef.current);
} else {
(window as Window).addEventListener('resize', buildGrid);
(window as Window).addEventListener("resize", buildGrid);
}
return () => {
if (ro) ro.disconnect();
else window.removeEventListener('resize', buildGrid);
else window.removeEventListener("resize", buildGrid);
};
}, [buildGrid]);
@@ -231,10 +231,10 @@ const DotGrid: React.FC<DotGridProps> = ({
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
ease: "elastic.out(1,0.75)",
});
dot._inertiaApplied = false;
}
},
});
}
}
@@ -259,29 +259,43 @@ const DotGrid: React.FC<DotGridProps> = ({
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
ease: "elastic.out(1,0.75)",
});
dot._inertiaApplied = false;
}
},
});
}
}
};
const throttledMove = throttle(onMove, 50);
window.addEventListener('mousemove', throttledMove, { passive: true });
window.addEventListener('click', onClick);
window.addEventListener("mousemove", throttledMove, { passive: true });
window.addEventListener("click", onClick);
return () => {
window.removeEventListener('mousemove', throttledMove);
window.removeEventListener('click', onClick);
window.removeEventListener("mousemove", throttledMove);
window.removeEventListener("click", onClick);
};
}, [maxSpeed, speedTrigger, proximity, resistance, returnDuration, shockRadius, shockStrength]);
}, [
maxSpeed,
speedTrigger,
proximity,
resistance,
returnDuration,
shockRadius,
shockStrength,
]);
return (
<section className={`p-4 flex items-center justify-center h-full w-full relative ${className}`} style={style}>
<section
className={`p-4 flex items-center justify-center h-full w-full relative ${className}`}
style={style}
>
<div ref={wrapperRef} className="w-full h-full relative">
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full pointer-events-none" />
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full pointer-events-none"
/>
</div>
</section>
);
+3 -3
View File
@@ -284,7 +284,7 @@ export default function FaultyTerminal({
const ditherValue = useMemo(
() => (typeof dither === "boolean" ? (dither ? 1 : 0) : dither),
[dither]
[dither],
);
const handleMouseMove = useCallback((e: MouseEvent) => {
@@ -316,7 +316,7 @@ export default function FaultyTerminal({
value: new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
gl.canvas.width / gl.canvas.height,
),
},
uScale: { value: scale },
@@ -354,7 +354,7 @@ export default function FaultyTerminal({
program.uniforms.iResolution.value = new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
gl.canvas.width / gl.canvas.height,
);
}
+109 -79
View File
@@ -1,4 +1,10 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
export type LogoItem =
| {
@@ -21,7 +27,7 @@ export type LogoItem =
export interface LogoLoopProps {
logos: LogoItem[];
speed?: number;
direction?: 'left' | 'right';
direction?: "left" | "right";
width?: number | string;
logoHeight?: number;
gap?: number;
@@ -37,28 +43,29 @@ export interface LogoLoopProps {
const ANIMATION_CONFIG = {
SMOOTH_TAU: 0.25,
MIN_COPIES: 2,
COPY_HEADROOM: 2
COPY_HEADROOM: 2,
} as const;
const toCssLength = (value?: number | string): string | undefined =>
typeof value === 'number' ? `${value}px` : (value ?? undefined);
typeof value === "number" ? `${value}px` : (value ?? undefined);
const cx = (...parts: Array<string | false | null | undefined>) => parts.filter(Boolean).join(' ');
const cx = (...parts: Array<string | false | null | undefined>) =>
parts.filter(Boolean).join(" ");
const useResizeObserver = (
callback: () => void,
elements: Array<React.RefObject<Element | null>>,
dependencies: React.DependencyList
dependencies: React.DependencyList,
) => {
useEffect(() => {
if (!window.ResizeObserver) {
const handleResize = () => callback();
window.addEventListener('resize', handleResize);
window.addEventListener("resize", handleResize);
callback();
return () => window.removeEventListener('resize', handleResize);
return () => window.removeEventListener("resize", handleResize);
}
const observers = elements.map(ref => {
const observers = elements.map((ref) => {
if (!ref.current) return null;
const observer = new ResizeObserver(callback);
observer.observe(ref.current);
@@ -68,7 +75,7 @@ const useResizeObserver = (
callback();
return () => {
observers.forEach(observer => observer?.disconnect());
observers.forEach((observer) => observer?.disconnect());
};
}, dependencies);
};
@@ -76,10 +83,10 @@ const useResizeObserver = (
const useImageLoader = (
seqRef: React.RefObject<HTMLUListElement | null>,
onLoad: () => void,
dependencies: React.DependencyList
dependencies: React.DependencyList,
) => {
useEffect(() => {
const images = seqRef.current?.querySelectorAll('img') ?? [];
const images = seqRef.current?.querySelectorAll("img") ?? [];
if (images.length === 0) {
onLoad();
@@ -94,20 +101,20 @@ const useImageLoader = (
}
};
images.forEach(img => {
images.forEach((img) => {
const htmlImg = img as HTMLImageElement;
if (htmlImg.complete) {
handleImageLoad();
} else {
htmlImg.addEventListener('load', handleImageLoad, { once: true });
htmlImg.addEventListener('error', handleImageLoad, { once: true });
htmlImg.addEventListener("load", handleImageLoad, { once: true });
htmlImg.addEventListener("error", handleImageLoad, { once: true });
}
});
return () => {
images.forEach(img => {
img.removeEventListener('load', handleImageLoad);
img.removeEventListener('error', handleImageLoad);
images.forEach((img) => {
img.removeEventListener("load", handleImageLoad);
img.removeEventListener("error", handleImageLoad);
});
};
}, dependencies);
@@ -118,7 +125,7 @@ const useAnimationLoop = (
targetVelocity: number,
seqWidth: number,
isHovered: boolean,
pauseOnHover: boolean
pauseOnHover: boolean,
) => {
const rafRef = useRef<number | null>(null);
const lastTimestampRef = useRef<number | null>(null);
@@ -130,17 +137,18 @@ const useAnimationLoop = (
if (!track) return;
const prefersReduced =
typeof window !== 'undefined' &&
typeof window !== "undefined" &&
window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (seqWidth > 0) {
offsetRef.current = ((offsetRef.current % seqWidth) + seqWidth) % seqWidth;
offsetRef.current =
((offsetRef.current % seqWidth) + seqWidth) % seqWidth;
track.style.transform = `translate3d(${-offsetRef.current}px, 0, 0)`;
}
if (prefersReduced) {
track.style.transform = 'translate3d(0, 0, 0)';
track.style.transform = "translate3d(0, 0, 0)";
return () => {
lastTimestampRef.current = null;
};
@@ -151,12 +159,14 @@ const useAnimationLoop = (
lastTimestampRef.current = timestamp;
}
const deltaTime = Math.max(0, timestamp - lastTimestampRef.current) / 1000;
const deltaTime =
Math.max(0, timestamp - lastTimestampRef.current) / 1000;
lastTimestampRef.current = timestamp;
const target = pauseOnHover && isHovered ? 0 : targetVelocity;
const easingFactor = 1 - Math.exp(-deltaTime / ANIMATION_CONFIG.SMOOTH_TAU);
const easingFactor =
1 - Math.exp(-deltaTime / ANIMATION_CONFIG.SMOOTH_TAU);
velocityRef.current += (target - velocityRef.current) * easingFactor;
if (seqWidth > 0) {
@@ -187,72 +197,87 @@ export const LogoLoop = React.memo<LogoLoopProps>(
({
logos,
speed = 120,
direction = 'left',
width = '100%',
direction = "left",
width = "100%",
logoHeight = 28,
gap = 32,
pauseOnHover = true,
fadeOut = false,
fadeOutColor,
scaleOnHover = false,
ariaLabel = 'Partner logos',
ariaLabel = "Partner logos",
className,
style
style,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const trackRef = useRef<HTMLDivElement>(null);
const seqRef = useRef<HTMLUListElement>(null);
const [seqWidth, setSeqWidth] = useState<number>(0);
const [copyCount, setCopyCount] = useState<number>(ANIMATION_CONFIG.MIN_COPIES);
const [copyCount, setCopyCount] = useState<number>(
ANIMATION_CONFIG.MIN_COPIES,
);
const [isHovered, setIsHovered] = useState<boolean>(false);
const targetVelocity = useMemo(() => {
const magnitude = Math.abs(speed);
const directionMultiplier = direction === 'left' ? 1 : -1;
const directionMultiplier = direction === "left" ? 1 : -1;
const speedMultiplier = speed < 0 ? -1 : 1;
return magnitude * directionMultiplier * speedMultiplier;
}, [speed, direction]);
const updateDimensions = useCallback(() => {
const containerWidth = containerRef.current?.clientWidth ?? 0;
const sequenceWidth = seqRef.current?.getBoundingClientRect?.()?.width ?? 0;
const sequenceWidth =
seqRef.current?.getBoundingClientRect?.()?.width ?? 0;
if (sequenceWidth > 0) {
setSeqWidth(Math.ceil(sequenceWidth));
const copiesNeeded = Math.ceil(containerWidth / sequenceWidth) + ANIMATION_CONFIG.COPY_HEADROOM;
const copiesNeeded =
Math.ceil(containerWidth / sequenceWidth) +
ANIMATION_CONFIG.COPY_HEADROOM;
setCopyCount(Math.max(ANIMATION_CONFIG.MIN_COPIES, copiesNeeded));
}
}, []);
useResizeObserver(updateDimensions, [containerRef, seqRef], [logos, gap, logoHeight]);
useResizeObserver(
updateDimensions,
[containerRef, seqRef],
[logos, gap, logoHeight],
);
useImageLoader(seqRef, updateDimensions, [logos, gap, logoHeight]);
useAnimationLoop(trackRef, targetVelocity, seqWidth, isHovered, pauseOnHover);
useAnimationLoop(
trackRef,
targetVelocity,
seqWidth,
isHovered,
pauseOnHover,
);
const cssVariables = useMemo(
() =>
({
'--logoloop-gap': `${gap}px`,
'--logoloop-logoHeight': `${logoHeight}px`,
...(fadeOutColor && { '--logoloop-fadeColor': fadeOutColor })
"--logoloop-gap": `${gap}px`,
"--logoloop-logoHeight": `${logoHeight}px`,
...(fadeOutColor && { "--logoloop-fadeColor": fadeOutColor }),
}) as React.CSSProperties,
[gap, logoHeight, fadeOutColor]
[gap, logoHeight, fadeOutColor],
);
const rootClasses = useMemo(
() =>
cx(
'relative overflow-x-hidden group',
'[--logoloop-gap:32px]',
'[--logoloop-logoHeight:28px]',
'[--logoloop-fadeColorAuto:#ffffff]',
'dark:[--logoloop-fadeColorAuto:#0b0b0b]',
scaleOnHover && 'py-[calc(var(--logoloop-logoHeight)*0.1)]',
className
"relative overflow-x-hidden group",
"[--logoloop-gap:32px]",
"[--logoloop-logoHeight:28px]",
"[--logoloop-fadeColorAuto:#ffffff]",
"dark:[--logoloop-fadeColorAuto:#0b0b0b]",
scaleOnHover && "py-[calc(var(--logoloop-logoHeight)*0.1)]",
className,
),
[scaleOnHover, className]
[scaleOnHover, className],
);
const handleMouseEnter = useCallback(() => {
@@ -265,15 +290,15 @@ export const LogoLoop = React.memo<LogoLoopProps>(
const renderLogoItem = useCallback(
(item: LogoItem, key: React.Key) => {
const isNodeItem = 'node' in item;
const isNodeItem = "node" in item;
const content = isNodeItem ? (
<span
className={cx(
'inline-flex items-center',
'motion-reduce:transition-none',
"inline-flex items-center",
"motion-reduce:transition-none",
scaleOnHover &&
'transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-hover/item:scale-120'
"transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-hover/item:scale-120",
)}
aria-hidden={!!(item as any).href && !(item as any).ariaLabel}
>
@@ -282,19 +307,19 @@ export const LogoLoop = React.memo<LogoLoopProps>(
) : (
<img
className={cx(
'h-[var(--logoloop-logoHeight)] w-auto block object-contain',
'[-webkit-user-drag:none] pointer-events-none',
'[image-rendering:-webkit-optimize-contrast]',
'motion-reduce:transition-none',
"h-[var(--logoloop-logoHeight)] w-auto block object-contain",
"[-webkit-user-drag:none] pointer-events-none",
"[image-rendering:-webkit-optimize-contrast]",
"motion-reduce:transition-none",
scaleOnHover &&
'transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-hover/item:scale-120'
"transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)] group-hover/item:scale-120",
)}
src={(item as any).src}
srcSet={(item as any).srcSet}
sizes={(item as any).sizes}
width={(item as any).width}
height={(item as any).height}
alt={(item as any).alt ?? ''}
alt={(item as any).alt ?? ""}
title={(item as any).title}
loading="lazy"
decoding="async"
@@ -309,13 +334,13 @@ export const LogoLoop = React.memo<LogoLoopProps>(
const inner = (item as any).href ? (
<a
className={cx(
'inline-flex items-center no-underline rounded',
'transition-opacity duration-200 ease-linear',
'hover:opacity-80',
'focus-visible:outline focus-visible:outline-current focus-visible:outline-offset-2'
"inline-flex items-center no-underline rounded",
"transition-opacity duration-200 ease-linear",
"hover:opacity-80",
"focus-visible:outline focus-visible:outline-current focus-visible:outline-offset-2",
)}
href={(item as any).href}
aria-label={itemAriaLabel || 'logo link'}
aria-label={itemAriaLabel || "logo link"}
target="_blank"
rel="noreferrer noopener"
>
@@ -328,8 +353,8 @@ export const LogoLoop = React.memo<LogoLoopProps>(
return (
<li
className={cx(
'flex-none mr-[var(--logoloop-gap)] text-[length:var(--logoloop-logoHeight)] leading-[1]',
scaleOnHover && 'overflow-visible group/item'
"flex-none mr-[var(--logoloop-gap)] text-[length:var(--logoloop-logoHeight)] leading-[1]",
scaleOnHover && "overflow-visible group/item",
)}
key={key}
role="listitem"
@@ -338,7 +363,7 @@ export const LogoLoop = React.memo<LogoLoopProps>(
</li>
);
},
[scaleOnHover]
[scaleOnHover],
);
const logoLists = useMemo(
@@ -351,19 +376,21 @@ export const LogoLoop = React.memo<LogoLoopProps>(
aria-hidden={copyIndex > 0}
ref={copyIndex === 0 ? seqRef : undefined}
>
{logos.map((item, itemIndex) => renderLogoItem(item, `${copyIndex}-${itemIndex}`))}
{logos.map((item, itemIndex) =>
renderLogoItem(item, `${copyIndex}-${itemIndex}`),
)}
</ul>
)),
[copyCount, logos, renderLogoItem]
[copyCount, logos, renderLogoItem],
);
const containerStyle = useMemo(
(): React.CSSProperties => ({
width: toCssLength(width) ?? '100%',
width: toCssLength(width) ?? "100%",
...cssVariables,
...style
...style,
}),
[width, cssVariables, style]
[width, cssVariables, style],
);
return (
@@ -381,33 +408,36 @@ export const LogoLoop = React.memo<LogoLoopProps>(
<div
aria-hidden
className={cx(
'pointer-events-none absolute inset-y-0 left-0 z-[1]',
'w-[clamp(24px,8%,120px)]',
'bg-[linear-gradient(to_right,var(--logoloop-fadeColor,var(--logoloop-fadeColorAuto))_0%,rgba(0,0,0,0)_100%)]'
"pointer-events-none absolute inset-y-0 left-0 z-[1]",
"w-[clamp(24px,8%,120px)]",
"bg-[linear-gradient(to_right,var(--logoloop-fadeColor,var(--logoloop-fadeColorAuto))_0%,rgba(0,0,0,0)_100%)]",
)}
/>
<div
aria-hidden
className={cx(
'pointer-events-none absolute inset-y-0 right-0 z-[1]',
'w-[clamp(24px,8%,120px)]',
'bg-[linear-gradient(to_left,var(--logoloop-fadeColor,var(--logoloop-fadeColorAuto))_0%,rgba(0,0,0,0)_100%)]'
"pointer-events-none absolute inset-y-0 right-0 z-[1]",
"w-[clamp(24px,8%,120px)]",
"bg-[linear-gradient(to_left,var(--logoloop-fadeColor,var(--logoloop-fadeColorAuto))_0%,rgba(0,0,0,0)_100%)]",
)}
/>
</>
)}
<div
className={cx('flex w-max will-change-transform select-none', 'motion-reduce:transform-none')}
className={cx(
"flex w-max will-change-transform select-none",
"motion-reduce:transform-none",
)}
ref={trackRef}
>
{logoLists}
</div>
</div>
);
}
},
);
LogoLoop.displayName = 'LogoLoop';
LogoLoop.displayName = "LogoLoop";
export default LogoLoop;
+1 -1
View File
@@ -80,7 +80,7 @@ const Squares: React.FC<SquaresProps> = ({
0,
canvas.width / 2,
canvas.height / 2,
Math.sqrt(canvas.width ** 2 + canvas.height ** 2) / 2
Math.sqrt(canvas.width ** 2 + canvas.height ** 2) / 2,
);
gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
gradient.addColorStop(1, "#060010");
+43 -27
View File
@@ -1,7 +1,15 @@
'use client';
"use client";
import { ElementType, useEffect, useRef, useState, createElement, useMemo, useCallback } from 'react';
import { gsap } from 'gsap';
import {
ElementType,
useEffect,
useRef,
useState,
createElement,
useMemo,
useCallback,
} from "react";
import { gsap } from "gsap";
interface TextTypeProps {
className?: string;
@@ -26,17 +34,17 @@ interface TextTypeProps {
const TextType = ({
text,
as: Component = 'div',
as: Component = "div",
typingSpeed = 50,
initialDelay = 0,
pauseDuration = 2000,
deletingSpeed = 30,
loop = true,
className = '',
className = "",
showCursor = true,
hideCursorWhileTyping = false,
cursorCharacter = '|',
cursorClassName = '',
cursorCharacter = "|",
cursorClassName = "",
cursorBlinkDuration = 0.5,
textColors = [],
variableSpeed,
@@ -45,7 +53,7 @@ const TextType = ({
reverseMode = false,
...props
}: TextTypeProps & React.HTMLAttributes<HTMLElement>) => {
const [displayedText, setDisplayedText] = useState('');
const [displayedText, setDisplayedText] = useState("");
const [currentCharIndex, setCurrentCharIndex] = useState(0);
const [isDeleting, setIsDeleting] = useState(false);
const [currentTextIndex, setCurrentTextIndex] = useState(0);
@@ -53,7 +61,10 @@ const TextType = ({
const cursorRef = useRef<HTMLSpanElement>(null);
const containerRef = useRef<HTMLElement>(null);
const textArray = useMemo(() => (Array.isArray(text) ? text : [text]), [text]);
const textArray = useMemo(
() => (Array.isArray(text) ? text : [text]),
[text],
);
const getRandomSpeed = useCallback(() => {
if (!variableSpeed) return typingSpeed;
@@ -62,7 +73,7 @@ const TextType = ({
}, [variableSpeed, typingSpeed]);
const getCurrentTextColor = () => {
if (textColors.length === 0) return '#ffffff';
if (textColors.length === 0) return "#ffffff";
return textColors[currentTextIndex % textColors.length];
};
@@ -70,14 +81,14 @@ const TextType = ({
if (!startOnVisible || !containerRef.current) return;
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
});
},
{ threshold: 0.1 }
{ threshold: 0.1 },
);
observer.observe(containerRef.current);
@@ -92,7 +103,7 @@ const TextType = ({
duration: cursorBlinkDuration,
repeat: -1,
yoyo: true,
ease: 'power2.inOut'
ease: "power2.inOut",
});
}
}, [showCursor, cursorBlinkDuration]);
@@ -103,11 +114,13 @@ const TextType = ({
let timeout: NodeJS.Timeout;
const currentText = textArray[currentTextIndex];
const processedText = reverseMode ? currentText.split('').reverse().join('') : currentText;
const processedText = reverseMode
? currentText.split("").reverse().join("")
: currentText;
const executeTypingAnimation = () => {
if (isDeleting) {
if (displayedText === '') {
if (displayedText === "") {
setIsDeleting(false);
if (currentTextIndex === textArray.length - 1 && !loop) {
return;
@@ -117,22 +130,24 @@ const TextType = ({
onSentenceComplete(textArray[currentTextIndex], currentTextIndex);
}
setCurrentTextIndex(prev => (prev + 1) % textArray.length);
setCurrentTextIndex((prev) => (prev + 1) % textArray.length);
setCurrentCharIndex(0);
timeout = setTimeout(() => {}, pauseDuration);
} else {
timeout = setTimeout(() => {
setDisplayedText(prev => prev.slice(0, -1));
setDisplayedText((prev) => prev.slice(0, -1));
}, deletingSpeed);
}
} else {
if (currentCharIndex < processedText.length) {
timeout = setTimeout(
() => {
setDisplayedText(prev => prev + processedText[currentCharIndex]);
setCurrentCharIndex(prev => prev + 1);
setDisplayedText(
(prev) => prev + processedText[currentCharIndex],
);
setCurrentCharIndex((prev) => prev + 1);
},
variableSpeed ? getRandomSpeed() : typingSpeed
variableSpeed ? getRandomSpeed() : typingSpeed,
);
} else if (textArray.length > 1) {
timeout = setTimeout(() => {
@@ -142,7 +157,7 @@ const TextType = ({
}
};
if (currentCharIndex === 0 && !isDeleting && displayedText === '') {
if (currentCharIndex === 0 && !isDeleting && displayedText === "") {
timeout = setTimeout(executeTypingAnimation, initialDelay);
} else {
executeTypingAnimation();
@@ -163,18 +178,19 @@ const TextType = ({
isVisible,
reverseMode,
variableSpeed,
onSentenceComplete
onSentenceComplete,
]);
const shouldHideCursor =
hideCursorWhileTyping && (currentCharIndex < textArray[currentTextIndex].length || isDeleting);
hideCursorWhileTyping &&
(currentCharIndex < textArray[currentTextIndex].length || isDeleting);
return createElement(
Component,
{
ref: containerRef,
className: `inline-block whitespace-pre-wrap tracking-tight ${className}`,
...props
...props,
},
<span className="inline" style={{ color: getCurrentTextColor() }}>
{displayedText}
@@ -182,11 +198,11 @@ const TextType = ({
showCursor && (
<span
ref={cursorRef}
className={`ml-1 inline-block opacity-100 ${shouldHideCursor ? 'hidden' : ''} ${cursorClassName}`}
className={`ml-1 inline-block opacity-100 ${shouldHideCursor ? "hidden" : ""} ${cursorClassName}`}
>
{cursorCharacter}
</span>
)
),
);
};
@@ -16,7 +16,7 @@ export function AnimatedGradientText({
<motion.span
className={cn(
"bg-gradient-to-r from-cyan-400 via-blue-500 to-blue-600 bg-clip-text text-transparent font-bold",
className
className,
)}
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
+1 -1
View File
@@ -13,7 +13,7 @@ export function AuroraText({ children, className }: AuroraTextProps) {
<motion.span
className={cn(
"bg-gradient-to-r from-blue-400 via-cyan-500 to-blue-500 bg-clip-text text-transparent",
className
className,
)}
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
+2 -2
View File
@@ -20,7 +20,7 @@ export function BentoGrid({ children, className }: BentoGridProps) {
<div
className={cn(
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl mx-auto justify-center",
className
className,
)}
>
{children}
@@ -39,7 +39,7 @@ export function BentoGridItem({
className={cn(
"relative overflow-hidden rounded-lg border border-white/20 bg-white/5 backdrop-blur-sm p-6",
`col-span-${colSpan} row-span-${rowSpan}`,
className
className,
)}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
+1 -1
View File
@@ -13,7 +13,7 @@ export function MagicCard({ children, className }: MagicCardProps) {
<motion.div
className={cn(
"relative overflow-hidden rounded-lg border border-white/20 bg-white/5 backdrop-blur-sm",
className
className,
)}
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
+1 -1
View File
@@ -18,7 +18,7 @@ export function ShimmerButton({
<motion.button
className={cn(
"relative overflow-hidden rounded-lg bg-gradient-to-r from-cyan-500 to-blue-600 px-6 py-3 font-semibold text-white shadow-lg",
className
className,
)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
+1 -1
View File
@@ -165,7 +165,7 @@ const Particles: React.FC<ParticlesProps> = ({
positions.set([x * r, y * r, z * r], i * 3);
randoms.set(
[Math.random(), Math.random(), Math.random(), Math.random()],
i * 4
i * 4,
);
const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);
colors.set(col, i * 3);