feat(ui): enhance contact form messaging and particle background animation

- Improved the contact form's message generation with more professional and detailed templates
- Replaced the grid-based background animation with a WebGL-powered particle system using OGL
- Added GSAP for advanced animation capabilities
- Adjusted layout styling for better centering and spacing across components
- Enhanced visual feedback and interactivity of the animated background

This change significantly upgrades the user experience and visual appeal of the contact section while maintaining performance through efficient rendering techniques.
This commit is contained in:
¨evandrodevbr¨ committed 2025-09-03 00:51:29 -03:00
1 parent 90cf3c73ad
commit 780fd7c161
13 files changed
+1230 -203

No files matched your search

+71 -43
View File
@@ -53,67 +53,95 @@ export function ContactForm({ formType, setFormType }: ContactFormProps) {
const getGreetingMessageWithUserName = () => {
const hour = new Date().getHours();
const dayPart =
hour >= 8 && hour < 12
? "manhã"
: hour >= 12 && hour < 18
? "tarde"
: "noite";
const baseMessage = (name: string) => {
if (hour >= 8 && hour < 12) {
// Manhã
return `Bom dia Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`;
} else if (hour >= 13 && hour < 18) {
// Tarde
return `Boa tarde Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar?`;
} else {
// Noite
return `Olá Evandro, meu nome é ${name}. Vi seu portfólio e estou interessado nos seus serviços. Podemos conversar quando estiver disponível?`;
}
};
return baseMessage(userName);
return `Olá Evandro, espero que esta ${dayPart} esteja sendo produtiva.
Meu nome é ${userName} e entrei em contato pois vi seu portfólio e identifiquei que suas habilidades como Desenvolvedor Full-Stack podem agregar muito ao projeto que tenho em mente.
Gostaria de agendar uma conversa para discutirmos detalhes e entender como podemos trabalhar juntos de forma eficiente e produtiva.
Fico no aguardo do seu contato para marcarmos um horário que seja conveniente para ambos.
Atenciosamente,
${userName}`;
};
const generateAdvancedMessage = () => {
if (customMessage) return customMessage;
let message = `Olá Evandro,\n\n`;
// Saudação profissional
let message = `Prezado Evandro,\n\n`;
// Informações do contato
message += `Meu nome é ${userName}`;
if (userCompany) {
message += ` e sou de ${userCompany}`;
}
if (howFound) {
message += `. Conheci seu trabalho via ${howFound}`;
}
message += `.\n\n`;
message += `Estou entrando em contato para discutir um projeto de `;
if (projectType === "outro" && otherProjectType) {
message += `${otherProjectType}`;
} else if (projectType) {
message += `${projectType}`;
} else {
message += `[Tipo de Projeto Não Especificado]`;
message += `, represento a empresa ${userCompany}`;
}
message += `.\n`;
if (desiredDeadline) {
message += `O prazo desejado é ${desiredDeadline}.\n`;
}
if (budgetValue) {
message += `O orçamento disponível é ${budgetValue}.\n`;
}
if (technicalComplexity) {
message += `A complexidade técnica estimada é ${technicalComplexity}.\n`;
}
if (hasInternalTeam) {
message += `Já possuímos uma equipe técnica ${hasInternalTeam}.\n`;
if (howFound) {
message += `Conheci seu trabalho através de ${howFound}.\n`;
}
message += `\nMeus contatos:\n`;
message += `\n`;
// Detalhes do projeto
message += `=== DETALHES DO PROJETO ===\n`;
message += `Tipo de projeto: `;
if (projectType === "outro" && otherProjectType) {
message += `${otherProjectType}\n`;
} else if (projectType) {
message += `${projectType}\n`;
} else {
message += `Não especificado\n`;
}
if (desiredDeadline) {
message += `Prazo desejado: ${desiredDeadline}\n`;
}
if (budgetValue) {
message += `Orçamento disponível: ${budgetValue}\n`;
}
if (technicalComplexity) {
message += `Complexidade técnica estimada: ${technicalComplexity}\n`;
}
if (hasInternalTeam) {
message += `Equipe técnica interna: ${
hasInternalTeam === "sim"
? "Sim"
: hasInternalTeam === "nao"
? "Não"
: "Parcialmente"
}\n`;
}
message += `\n`;
// Contatos
message += `=== INFORMAÇÕES PARA CONTATO ===\n`;
if (userEmail) {
message += `Email: ${userEmail}\n`;
message += `📧 Email: ${userEmail}\n`;
}
if (userPhone) {
message += `Telefone: ${userPhone}\n`;
message += `📱 Telefone/WhatsApp: ${userPhone}\n`;
}
message += `\nAguardamos seu retorno para discutir os próximos passos.`;
message += `\n`;
// Mensagem final
message += `Estou entrando em contato pois acredito que seus conhecimentos como Desenvolvedor Full-Stack podem agregar muito ao projeto que tenho em mente.\n\n`;
message += `Fico no aguardo do seu contato para discutirmos detalhes e definirmos os próximos passos.\n\n`;
message += `Atenciosamente,\n`;
message += `${userName}`;
return message;
};
+1 -1
View File
@@ -36,7 +36,7 @@ export default function Experience() {
return (
<section
id="experience"
className="py-20 px-4 bg-gradient-to-b from-transparent to-black/20"
className="py-20 px-4 bg-gradient-to-b from-transparent to-black/20 mx-auto"
>
<div className="max-w-6xl mx-auto">
<motion.div
+2 -2
View File
@@ -86,8 +86,8 @@ export default function Hero() {
className="text-xl md:text-2xl lg:text-3xl text-gray-300 max-w-3xl mx-auto mb-12 leading-relaxed"
>
Especialista em soluções web modernas e inovadoras. Formado em
Engenharia da Computação, com conhecimentos sólidos em computação em
nuvem e inteligência artificial.
Engenharia da Computação, com conhecimentos em computação em nuvem e
inteligência artificial.
</motion.p>
{/* Location */}
+1 -1
View File
@@ -34,7 +34,7 @@ export default function Projects() {
const projects = data.projects.filter((project) => project.featured);
return (
<section id="projects" className="py-20 px-4">
<section id="projects" className="py-20 px-4 mx-auto">
<div className="max-w-6xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 30 }}
+1 -1
View File
@@ -74,7 +74,7 @@ export function WorkModalities() {
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-6xl mx-auto justify-center">
{WorkModalitiesData.map((modality, index) => (
<motion.div
key={modality.title}
+11 -9
View File
@@ -1,7 +1,8 @@
import dynamic from "next/dynamic";
import { Header } from "./components/Header";
import { Squares } from "@/components/magicui/squares";
import Squares from "@/components/Squares";
import { WorkModalities } from "./components/WorkModalities"; // Importar WorkModalities
import DotGrid from "@/components/DotGrid";
// Lazy load components for better performance
const Hero = dynamic(() => import("./components/Hero"), {
@@ -55,14 +56,15 @@ const Contact = dynamic(() => import("./components/Contact"), {
export default function Home() {
return (
<div className="min-h-screen bg-black text-white relative">
<Squares
direction="diagonal"
speed={0.5}
borderColor="#374151"
hoverFillColor="#1e40af"
squareSize={30}
className="absolute inset-0 opacity-30"
/>
<div className="fixed inset-0 z-0 opacity-50">
<Squares
speed={0.5}
squareSize={40}
direction="diagonal" // up, down, left, right, diagonal
borderColor="#271E37"
hoverFillColor="#222222"
/>
</div>
<Header />
<main className="relative z-10">
<Hero />
+290
View File
@@ -0,0 +1,290 @@
'use client';
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
import { gsap } from 'gsap';
import { InertiaPlugin } from 'gsap/InertiaPlugin';
gsap.registerPlugin(InertiaPlugin);
const throttle = (func: (...args: any[]) => void, limit: number) => {
let lastCall = 0;
return function (this: any, ...args: any[]) {
const now = performance.now();
if (now - lastCall >= limit) {
lastCall = now;
func.apply(this, args);
}
};
};
interface Dot {
cx: number;
cy: number;
xOffset: number;
yOffset: number;
_inertiaApplied: boolean;
}
export interface DotGridProps {
dotSize?: number;
gap?: number;
baseColor?: string;
activeColor?: string;
proximity?: number;
speedTrigger?: number;
shockRadius?: number;
shockStrength?: number;
maxSpeed?: number;
resistance?: number;
returnDuration?: number;
className?: string;
style?: React.CSSProperties;
}
function hexToRgb(hex: string) {
const m = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
if (!m) return { r: 0, g: 0, b: 0 };
return {
r: parseInt(m[1], 16),
g: parseInt(m[2], 16),
b: parseInt(m[3], 16)
};
}
const DotGrid: React.FC<DotGridProps> = ({
dotSize = 16,
gap = 32,
baseColor = '#5227FF',
activeColor = '#5227FF',
proximity = 150,
speedTrigger = 100,
shockRadius = 250,
shockStrength = 5,
maxSpeed = 5000,
resistance = 750,
returnDuration = 1.5,
className = '',
style
}) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const dotsRef = useRef<Dot[]>([]);
const pointerRef = useRef({
x: 0,
y: 0,
vx: 0,
vy: 0,
speed: 0,
lastTime: 0,
lastX: 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;
const p = new Path2D();
p.arc(0, 0, dotSize / 2, 0, Math.PI * 2);
return p;
}, [dotSize]);
const buildGrid = useCallback(() => {
const wrap = wrapperRef.current;
const canvas = canvasRef.current;
if (!wrap || !canvas) return;
const { width, height } = wrap.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext('2d');
if (ctx) ctx.scale(dpr, dpr);
const cols = Math.floor((width + gap) / (dotSize + gap));
const rows = Math.floor((height + gap) / (dotSize + gap));
const cell = dotSize + gap;
const gridW = cell * cols - gap;
const gridH = cell * rows - gap;
const extraX = width - gridW;
const extraY = height - gridH;
const startX = extraX / 2 + dotSize / 2;
const startY = extraY / 2 + dotSize / 2;
const dots: Dot[] = [];
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const cx = startX + x * cell;
const cy = startY + y * cell;
dots.push({ cx, cy, xOffset: 0, yOffset: 0, _inertiaApplied: false });
}
}
dotsRef.current = dots;
}, [dotSize, gap]);
useEffect(() => {
if (!circlePath) return;
let rafId: number;
const proxSq = proximity * proximity;
const draw = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const { x: px, y: py } = pointerRef.current;
for (const dot of dotsRef.current) {
const ox = dot.cx + dot.xOffset;
const oy = dot.cy + dot.yOffset;
const dx = dot.cx - px;
const dy = dot.cy - py;
const dsq = dx * dx + dy * dy;
let style = baseColor;
if (dsq <= proxSq) {
const dist = Math.sqrt(dsq);
const t = 1 - dist / proximity;
const r = Math.round(baseRgb.r + (activeRgb.r - baseRgb.r) * t);
const g = Math.round(baseRgb.g + (activeRgb.g - baseRgb.g) * t);
const b = Math.round(baseRgb.b + (activeRgb.b - baseRgb.b) * t);
style = `rgb(${r},${g},${b})`;
}
ctx.save();
ctx.translate(ox, oy);
ctx.fillStyle = style;
ctx.fill(circlePath);
ctx.restore();
}
rafId = requestAnimationFrame(draw);
};
draw();
return () => cancelAnimationFrame(rafId);
}, [proximity, baseColor, activeRgb, baseRgb, circlePath]);
useEffect(() => {
buildGrid();
let ro: ResizeObserver | null = null;
if ('ResizeObserver' in window) {
ro = new ResizeObserver(buildGrid);
wrapperRef.current && ro.observe(wrapperRef.current);
} else {
(window as Window).addEventListener('resize', buildGrid);
}
return () => {
if (ro) ro.disconnect();
else window.removeEventListener('resize', buildGrid);
};
}, [buildGrid]);
useEffect(() => {
const onMove = (e: MouseEvent) => {
const now = performance.now();
const pr = pointerRef.current;
const dt = pr.lastTime ? now - pr.lastTime : 16;
const dx = e.clientX - pr.lastX;
const dy = e.clientY - pr.lastY;
let vx = (dx / dt) * 1000;
let vy = (dy / dt) * 1000;
let speed = Math.hypot(vx, vy);
if (speed > maxSpeed) {
const scale = maxSpeed / speed;
vx *= scale;
vy *= scale;
speed = maxSpeed;
}
pr.lastTime = now;
pr.lastX = e.clientX;
pr.lastY = e.clientY;
pr.vx = vx;
pr.vy = vy;
pr.speed = speed;
const rect = canvasRef.current!.getBoundingClientRect();
pr.x = e.clientX - rect.left;
pr.y = e.clientY - rect.top;
for (const dot of dotsRef.current) {
const dist = Math.hypot(dot.cx - pr.x, dot.cy - pr.y);
if (speed > speedTrigger && dist < proximity && !dot._inertiaApplied) {
dot._inertiaApplied = true;
gsap.killTweensOf(dot);
const pushX = dot.cx - pr.x + vx * 0.005;
const pushY = dot.cy - pr.y + vy * 0.005;
gsap.to(dot, {
inertia: { xOffset: pushX, yOffset: pushY, resistance },
onComplete: () => {
gsap.to(dot, {
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
});
dot._inertiaApplied = false;
}
});
}
}
};
const onClick = (e: MouseEvent) => {
const rect = canvasRef.current!.getBoundingClientRect();
const cx = e.clientX - rect.left;
const cy = e.clientY - rect.top;
for (const dot of dotsRef.current) {
const dist = Math.hypot(dot.cx - cx, dot.cy - cy);
if (dist < shockRadius && !dot._inertiaApplied) {
dot._inertiaApplied = true;
gsap.killTweensOf(dot);
const falloff = Math.max(0, 1 - dist / shockRadius);
const pushX = (dot.cx - cx) * shockStrength * falloff;
const pushY = (dot.cy - cy) * shockStrength * falloff;
gsap.to(dot, {
inertia: { xOffset: pushX, yOffset: pushY, resistance },
onComplete: () => {
gsap.to(dot, {
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
});
dot._inertiaApplied = false;
}
});
}
}
};
const throttledMove = throttle(onMove, 50);
window.addEventListener('mousemove', throttledMove, { passive: true });
window.addEventListener('click', onClick);
return () => {
window.removeEventListener('mousemove', throttledMove);
window.removeEventListener('click', onClick);
};
}, [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}>
<div ref={wrapperRef} className="w-full h-full relative">
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full pointer-events-none" />
</div>
</section>
);
};
export default DotGrid;
+445
View File
@@ -0,0 +1,445 @@
"use client";
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
import React, { useEffect, useRef, useMemo, useCallback } from "react";
type Vec2 = [number, number];
export interface FaultyTerminalProps
extends React.HTMLAttributes<HTMLDivElement> {
scale?: number;
gridMul?: Vec2;
digitSize?: number;
timeScale?: number;
pause?: boolean;
scanlineIntensity?: number;
glitchAmount?: number;
flickerAmount?: number;
noiseAmp?: number;
chromaticAberration?: number;
dither?: number | boolean;
curvature?: number;
tint?: string;
mouseReact?: boolean;
mouseStrength?: number;
dpr?: number;
pageLoadAnimation?: boolean;
brightness?: number;
}
const vertexShader = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
varying vec2 vUv;
uniform float iTime;
uniform vec3 iResolution;
uniform float uScale;
uniform vec2 uGridMul;
uniform float uDigitSize;
uniform float uScanlineIntensity;
uniform float uGlitchAmount;
uniform float uFlickerAmount;
uniform float uNoiseAmp;
uniform float uChromaticAberration;
uniform float uDither;
uniform float uCurvature;
uniform vec3 uTint;
uniform vec2 uMouse;
uniform float uMouseStrength;
uniform float uUseMouse;
uniform float uPageLoadProgress;
uniform float uUsePageLoadAnimation;
uniform float uBrightness;
float time;
float hash21(vec2 p){
p = fract(p * 234.56);
p += dot(p, p + 34.56);
return fract(p.x * p.y);
}
float noise(vec2 p)
{
return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2;
}
mat2 rotate(float angle)
{
float c = cos(angle);
float s = sin(angle);
return mat2(c, -s, s, c);
}
float fbm(vec2 p)
{
p *= 1.1;
float f = 0.0;
float amp = 0.5 * uNoiseAmp;
mat2 modify0 = rotate(time * 0.02);
f += amp * noise(p);
p = modify0 * p * 2.0;
amp *= 0.454545;
mat2 modify1 = rotate(time * 0.02);
f += amp * noise(p);
p = modify1 * p * 2.0;
amp *= 0.454545;
mat2 modify2 = rotate(time * 0.08);
f += amp * noise(p);
return f;
}
float pattern(vec2 p, out vec2 q, out vec2 r) {
vec2 offset1 = vec2(1.0);
vec2 offset0 = vec2(0.0);
mat2 rot01 = rotate(0.1 * time);
mat2 rot1 = rotate(0.1);
q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));
r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));
return fbm(p + r);
}
float digit(vec2 p){
vec2 grid = uGridMul * 15.0;
vec2 s = floor(p * grid) / grid;
p = p * grid;
vec2 q, r;
float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;
if(uUseMouse > 0.5){
vec2 mouseWorld = uMouse * uScale;
float distToMouse = distance(s, mouseWorld);
float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;
intensity += mouseInfluence;
float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;
intensity += ripple;
}
if(uUsePageLoadAnimation > 0.5){
float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);
float cellDelay = cellRandom * 0.8;
float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);
float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);
intensity *= fadeAlpha;
}
p = fract(p);
p *= uDigitSize;
float px5 = p.x * 5.0;
float py5 = (1.0 - p.y) * 5.0;
float x = fract(px5);
float y = fract(py5);
float i = floor(py5) - 2.0;
float j = floor(px5) - 2.0;
float n = i * i + j * j;
float f = n * 0.0625;
float isOn = step(0.1, intensity - f);
float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);
return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;
}
float onOff(float a, float b, float c)
{
return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;
}
float displace(vec2 look)
{
float y = look.y - mod(iTime * 0.25, 1.0);
float window = 1.0 / (1.0 + 50.0 * y * y);
return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;
}
vec3 getColor(vec2 p){
float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;
bar *= uScanlineIntensity;
float displacement = displace(p);
p.x += displacement;
if (uGlitchAmount != 1.0) {
float extra = displacement * (uGlitchAmount - 1.0);
p.x += extra;
}
float middle = digit(p);
const float off = 0.002;
float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +
digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +
digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));
vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;
return baseColor;
}
vec2 barrel(vec2 uv){
vec2 c = uv * 2.0 - 1.0;
float r2 = dot(c, c);
c *= 1.0 + uCurvature * r2;
return c * 0.5 + 0.5;
}
void main() {
time = iTime * 0.333333;
vec2 uv = vUv;
if(uCurvature != 0.0){
uv = barrel(uv);
}
vec2 p = uv * uScale;
vec3 col = getColor(p);
if(uChromaticAberration != 0.0){
vec2 ca = vec2(uChromaticAberration) / iResolution.xy;
col.r = getColor(p + ca).r;
col.b = getColor(p - ca).b;
}
col *= uTint;
col *= uBrightness;
if(uDither > 0.0){
float rnd = hash21(gl_FragCoord.xy);
col += (rnd - 0.5) * (uDither * 0.003922);
}
gl_FragColor = vec4(col, 1.0);
}
`;
function hexToRgb(hex: string): [number, number, number] {
let h = hex.replace("#", "").trim();
if (h.length === 3)
h = h
.split("")
.map((c) => c + c)
.join("");
const num = parseInt(h, 16);
return [
((num >> 16) & 255) / 255,
((num >> 8) & 255) / 255,
(num & 255) / 255,
];
}
export default function FaultyTerminal({
scale = 1,
gridMul = [2, 1],
digitSize = 1.5,
timeScale = 0.3,
pause = false,
scanlineIntensity = 0.3,
glitchAmount = 1,
flickerAmount = 1,
noiseAmp = 1,
chromaticAberration = 0,
dither = 0,
curvature = 0.2,
tint = "#ffffff",
mouseReact = true,
mouseStrength = 0.2,
dpr = Math.min(window.devicePixelRatio || 1, 2),
pageLoadAnimation = true,
brightness = 1,
className,
style,
...rest
}: FaultyTerminalProps) {
const containerRef = useRef<HTMLDivElement>(null);
const programRef = useRef<Program>(null);
const rendererRef = useRef<Renderer>(null);
const mouseRef = useRef({ x: 0.5, y: 0.5 });
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
const frozenTimeRef = useRef(0);
const rafRef = useRef<number>(0);
const loadAnimationStartRef = useRef<number>(0);
const timeOffsetRef = useRef<number>(Math.random() * 100);
const tintVec = useMemo(() => hexToRgb(tint), [tint]);
const ditherValue = useMemo(
() => (typeof dither === "boolean" ? (dither ? 1 : 0) : dither),
[dither]
);
const handleMouseMove = useCallback((e: MouseEvent) => {
const ctn = containerRef.current;
if (!ctn) return;
const rect = ctn.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1 - (e.clientY - rect.top) / rect.height;
mouseRef.current = { x, y };
}, []);
useEffect(() => {
const ctn = containerRef.current;
if (!ctn) return;
const renderer = new Renderer({ dpr });
rendererRef.current = renderer;
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 1);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
iTime: { value: 0 },
iResolution: {
value: new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
),
},
uScale: { value: scale },
uGridMul: { value: new Float32Array(gridMul) },
uDigitSize: { value: digitSize },
uScanlineIntensity: { value: scanlineIntensity },
uGlitchAmount: { value: glitchAmount },
uFlickerAmount: { value: flickerAmount },
uNoiseAmp: { value: noiseAmp },
uChromaticAberration: { value: chromaticAberration },
uDither: { value: ditherValue },
uCurvature: { value: curvature },
uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },
uMouse: {
value: new Float32Array([
smoothMouseRef.current.x,
smoothMouseRef.current.y,
]),
},
uMouseStrength: { value: mouseStrength },
uUseMouse: { value: mouseReact ? 1 : 0 },
uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },
uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },
uBrightness: { value: brightness },
},
});
programRef.current = program;
const mesh = new Mesh(gl, { geometry, program });
function resize() {
if (!ctn || !renderer) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
program.uniforms.iResolution.value = new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
);
}
const resizeObserver = new ResizeObserver(() => resize());
resizeObserver.observe(ctn);
resize();
const update = (t: number) => {
rafRef.current = requestAnimationFrame(update);
if (pageLoadAnimation && loadAnimationStartRef.current === 0) {
loadAnimationStartRef.current = t;
}
if (!pause) {
const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;
program.uniforms.iTime.value = elapsed;
frozenTimeRef.current = elapsed;
} else {
program.uniforms.iTime.value = frozenTimeRef.current;
}
if (pageLoadAnimation && loadAnimationStartRef.current > 0) {
const animationDuration = 2000;
const animationElapsed = t - loadAnimationStartRef.current;
const progress = Math.min(animationElapsed / animationDuration, 1);
program.uniforms.uPageLoadProgress.value = progress;
}
if (mouseReact) {
const dampingFactor = 0.08;
const smoothMouse = smoothMouseRef.current;
const mouse = mouseRef.current;
smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;
smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;
const mouseUniform = program.uniforms.uMouse.value as Float32Array;
mouseUniform[0] = smoothMouse.x;
mouseUniform[1] = smoothMouse.y;
}
renderer.render({ scene: mesh });
};
rafRef.current = requestAnimationFrame(update);
ctn.appendChild(gl.canvas);
if (mouseReact) ctn.addEventListener("mousemove", handleMouseMove);
return () => {
cancelAnimationFrame(rafRef.current);
resizeObserver.disconnect();
if (mouseReact) ctn.removeEventListener("mousemove", handleMouseMove);
if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);
gl.getExtension("WEBGL_lose_context")?.loseContext();
loadAnimationStartRef.current = 0;
timeOffsetRef.current = Math.random() * 100;
};
}, [
dpr,
pause,
timeScale,
scale,
gridMul,
digitSize,
scanlineIntensity,
glitchAmount,
flickerAmount,
noiseAmp,
chromaticAberration,
ditherValue,
curvature,
tintVec,
mouseReact,
mouseStrength,
pageLoadAnimation,
brightness,
handleMouseMove,
]);
return (
<div
ref={containerRef}
className={`w-full h-full relative overflow-hidden ${className}`}
style={style}
{...rest}
/>
);
}
+172
View File
@@ -0,0 +1,172 @@
"use client";
import React, { useRef, useEffect } from "react";
type CanvasStrokeStyle = string | CanvasGradient | CanvasPattern;
interface GridOffset {
x: number;
y: number;
}
interface SquaresProps {
direction?: "diagonal" | "up" | "right" | "down" | "left";
speed?: number;
borderColor?: CanvasStrokeStyle;
squareSize?: number;
hoverFillColor?: CanvasStrokeStyle;
}
const Squares: React.FC<SquaresProps> = ({
direction = "right",
speed = 1,
borderColor = "#999",
squareSize = 40,
hoverFillColor = "#222",
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const requestRef = useRef<number | null>(null);
const numSquaresX = useRef<number>(0);
const numSquaresY = useRef<number>(0);
const gridOffset = useRef<GridOffset>({ x: 0, y: 0 });
const hoveredSquareRef = useRef<GridOffset | null>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
const resizeCanvas = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
numSquaresX.current = Math.ceil(canvas.width / squareSize) + 1;
numSquaresY.current = Math.ceil(canvas.height / squareSize) + 1;
};
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
const drawGrid = () => {
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize;
const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize;
for (let x = startX; x < canvas.width + squareSize; x += squareSize) {
for (let y = startY; y < canvas.height + squareSize; y += squareSize) {
const squareX = x - (gridOffset.current.x % squareSize);
const squareY = y - (gridOffset.current.y % squareSize);
if (
hoveredSquareRef.current &&
Math.floor((x - startX) / squareSize) ===
hoveredSquareRef.current.x &&
Math.floor((y - startY) / squareSize) === hoveredSquareRef.current.y
) {
ctx.fillStyle = hoverFillColor;
ctx.fillRect(squareX, squareY, squareSize, squareSize);
}
ctx.strokeStyle = borderColor;
ctx.strokeRect(squareX, squareY, squareSize, squareSize);
}
}
const gradient = ctx.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
Math.sqrt(canvas.width ** 2 + canvas.height ** 2) / 2
);
gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
gradient.addColorStop(1, "#060010");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
const updateAnimation = () => {
const effectiveSpeed = Math.max(speed, 0.1);
switch (direction) {
case "right":
gridOffset.current.x =
(gridOffset.current.x - effectiveSpeed + squareSize) % squareSize;
break;
case "left":
gridOffset.current.x =
(gridOffset.current.x + effectiveSpeed + squareSize) % squareSize;
break;
case "up":
gridOffset.current.y =
(gridOffset.current.y + effectiveSpeed + squareSize) % squareSize;
break;
case "down":
gridOffset.current.y =
(gridOffset.current.y - effectiveSpeed + squareSize) % squareSize;
break;
case "diagonal":
gridOffset.current.x =
(gridOffset.current.x - effectiveSpeed + squareSize) % squareSize;
gridOffset.current.y =
(gridOffset.current.y - effectiveSpeed + squareSize) % squareSize;
break;
default:
break;
}
drawGrid();
requestRef.current = requestAnimationFrame(updateAnimation);
};
const handleMouseMove = (event: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize;
const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize;
const hoveredSquareX = Math.floor(
(mouseX + gridOffset.current.x - startX) / squareSize
);
const hoveredSquareY = Math.floor(
(mouseY + gridOffset.current.y - startY) / squareSize
);
if (
!hoveredSquareRef.current ||
hoveredSquareRef.current.x !== hoveredSquareX ||
hoveredSquareRef.current.y !== hoveredSquareY
) {
hoveredSquareRef.current = { x: hoveredSquareX, y: hoveredSquareY };
}
};
const handleMouseLeave = () => {
hoveredSquareRef.current = null;
};
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mouseleave", handleMouseLeave);
requestRef.current = requestAnimationFrame(updateAnimation);
return () => {
window.removeEventListener("resize", resizeCanvas);
if (requestRef.current) cancelAnimationFrame(requestRef.current);
canvas.removeEventListener("mousemove", handleMouseMove);
canvas.removeEventListener("mouseleave", handleMouseLeave);
};
}, [direction, speed, borderColor, hoverFillColor, squareSize]);
return (
<canvas
ref={canvasRef}
className="w-full h-full border-none block"
></canvas>
);
};
export default Squares;
+1 -1
View File
@@ -19,7 +19,7 @@ export function BentoGrid({ children, className }: BentoGridProps) {
return (
<div
className={cn(
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl mx-auto",
"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl mx-auto justify-center",
className
)}
>
+217 -145
View File
@@ -1,181 +1,253 @@
"use client";
import React, { useEffect, useRef } from "react";
import { Renderer, Camera, Geometry, Program, Mesh } from "ogl";
import { useRef, useEffect, useState } from "react";
interface SquaresProps {
direction?: "right" | "left" | "up" | "down" | "diagonal";
interface ParticlesProps {
particleCount?: number;
particleSpread?: number;
speed?: number;
borderColor?: string;
squareSize?: number;
hoverFillColor?: string;
particleColors?: string[];
moveParticlesOnHover?: boolean;
particleHoverFactor?: number;
alphaParticles?: boolean;
particleBaseSize?: number;
sizeRandomness?: number;
cameraDistance?: number;
disableRotation?: boolean;
className?: string;
}
export function Squares({
direction = "right",
speed = 1,
borderColor = "#333",
squareSize = 40,
hoverFillColor = "#222",
const defaultColors: string[] = ["#ffffff", "#ffffff", "#ffffff"];
const hexToRgb = (hex: string): [number, number, number] => {
hex = hex.replace(/^#/, "");
if (hex.length === 3) {
hex = hex
.split("")
.map((c) => c + c)
.join("");
}
const int = parseInt(hex, 16);
const r = ((int >> 16) & 255) / 255;
const g = ((int >> 8) & 255) / 255;
const b = (int & 255) / 255;
return [r, g, b];
};
const vertex = /* glsl */ `
attribute vec3 position;
attribute vec4 random;
attribute vec3 color;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform float uTime;
uniform float uSpread;
uniform float uBaseSize;
uniform float uSizeRandomness;
varying vec4 vRandom;
varying vec3 vColor;
void main() {
vRandom = random;
vColor = color;
vec3 pos = position * uSpread;
pos.z *= 10.0;
vec4 mPos = modelMatrix * vec4(pos, 1.0);
float t = uTime;
mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);
mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);
mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);
vec4 mvPos = viewMatrix * mPos;
gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);
gl_Position = projectionMatrix * mvPos;
}
`;
const fragment = /* glsl */ `
precision highp float;
uniform float uTime;
uniform float uAlphaParticles;
varying vec4 vRandom;
varying vec3 vColor;
void main() {
vec2 uv = gl_PointCoord.xy;
float d = length(uv - vec2(0.5));
if(uAlphaParticles < 0.5) {
if(d > 0.5) {
discard;
}
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);
} else {
float circle = smoothstep(0.5, 0.4, d) * 0.8;
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);
}
}
`;
const Particles: React.FC<ParticlesProps> = ({
particleCount = 200,
particleSpread = 10,
speed = 0.1,
particleColors,
moveParticlesOnHover = false,
particleHoverFactor = 1,
alphaParticles = false,
particleBaseSize = 100,
sizeRandomness = 1,
cameraDistance = 20,
disableRotation = false,
className,
}: SquaresProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const requestRef = useRef<number | undefined>(undefined);
const numSquaresX = useRef<number | undefined>(undefined);
const numSquaresY = useRef<number | undefined>(undefined);
const gridOffset = useRef({ x: 0, y: 0 });
const [hoveredSquare, setHoveredSquare] = useState<{
x: number;
y: number;
} | null>(null);
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const container = containerRef.current;
if (!container) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const renderer = new Renderer({ depth: false, alpha: true });
const gl = renderer.gl;
container.appendChild(gl.canvas);
gl.clearColor(0, 0, 0, 0);
// Set canvas background
canvas.style.background = "#060606";
const camera = new Camera(gl, { fov: 15 });
camera.position.set(0, 0, cameraDistance);
const resizeCanvas = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
numSquaresX.current = Math.ceil(canvas.width / squareSize) + 1;
numSquaresY.current = Math.ceil(canvas.height / squareSize) + 1;
const resize = () => {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width, height);
camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
};
window.addEventListener("resize", resize, false);
resize();
const handleMouseMove = (e: MouseEvent) => {
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
mouseRef.current = { x, y };
};
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
if (moveParticlesOnHover) {
container.addEventListener("mousemove", handleMouseMove);
}
const drawGrid = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const count = particleCount;
const positions = new Float32Array(count * 3);
const randoms = new Float32Array(count * 4);
const colors = new Float32Array(count * 3);
const palette =
particleColors && particleColors.length > 0
? particleColors
: defaultColors;
const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize;
const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize;
for (let i = 0; i < count; i++) {
let x: number, y: number, z: number, len: number;
do {
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
z = Math.random() * 2 - 1;
len = x * x + y * y + z * z;
} while (len > 1 || len === 0);
const r = Math.cbrt(Math.random());
positions.set([x * r, y * r, z * r], i * 3);
randoms.set(
[Math.random(), Math.random(), Math.random(), Math.random()],
i * 4
);
const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);
colors.set(col, i * 3);
}
ctx.lineWidth = 0.5;
const geometry = new Geometry(gl, {
position: { size: 3, data: positions },
random: { size: 4, data: randoms },
color: { size: 3, data: colors },
});
for (let x = startX; x < canvas.width + squareSize; x += squareSize) {
for (let y = startY; y < canvas.height + squareSize; y += squareSize) {
const squareX = x - (gridOffset.current.x % squareSize);
const squareY = y - (gridOffset.current.y % squareSize);
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
uSpread: { value: particleSpread },
uBaseSize: { value: particleBaseSize },
uSizeRandomness: { value: sizeRandomness },
uAlphaParticles: { value: alphaParticles ? 1 : 0 },
},
transparent: true,
depthTest: false,
});
if (
hoveredSquare &&
Math.floor((x - startX) / squareSize) === hoveredSquare.x &&
Math.floor((y - startY) / squareSize) === hoveredSquare.y
) {
ctx.fillStyle = hoverFillColor;
ctx.fillRect(squareX, squareY, squareSize, squareSize);
}
const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });
ctx.strokeStyle = borderColor;
ctx.strokeRect(squareX, squareY, squareSize, squareSize);
}
let animationFrameId: number;
let lastTime = performance.now();
let elapsed = 0;
const update = (t: number) => {
animationFrameId = requestAnimationFrame(update);
const delta = t - lastTime;
lastTime = t;
elapsed += delta * speed;
program.uniforms.uTime.value = elapsed * 0.001;
if (moveParticlesOnHover) {
particles.position.x = -mouseRef.current.x * particleHoverFactor;
particles.position.y = -mouseRef.current.y * particleHoverFactor;
} else {
particles.position.x = 0;
particles.position.y = 0;
}
const gradient = ctx.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2)) / 2
);
gradient.addColorStop(0, "rgba(6, 6, 6, 0)");
gradient.addColorStop(1, "#060606");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
const updateAnimation = () => {
const effectiveSpeed = Math.max(speed, 0.1);
switch (direction) {
case "right":
gridOffset.current.x =
(gridOffset.current.x - effectiveSpeed + squareSize) % squareSize;
break;
case "left":
gridOffset.current.x =
(gridOffset.current.x + effectiveSpeed + squareSize) % squareSize;
break;
case "up":
gridOffset.current.y =
(gridOffset.current.y + effectiveSpeed + squareSize) % squareSize;
break;
case "down":
gridOffset.current.y =
(gridOffset.current.y - effectiveSpeed + squareSize) % squareSize;
break;
case "diagonal":
gridOffset.current.x =
(gridOffset.current.x - effectiveSpeed + squareSize) % squareSize;
gridOffset.current.y =
(gridOffset.current.y - effectiveSpeed + squareSize) % squareSize;
break;
if (!disableRotation) {
particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;
particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;
particles.rotation.z += 0.01 * speed;
}
drawGrid();
requestRef.current = requestAnimationFrame(updateAnimation);
renderer.render({ scene: particles, camera });
};
const handleMouseMove = (event: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
animationFrameId = requestAnimationFrame(update);
const startX = Math.floor(gridOffset.current.x / squareSize) * squareSize;
const startY = Math.floor(gridOffset.current.y / squareSize) * squareSize;
const hoveredSquareX = Math.floor(
(mouseX + gridOffset.current.x - startX) / squareSize
);
const hoveredSquareY = Math.floor(
(mouseY + gridOffset.current.y - startY) / squareSize
);
setHoveredSquare({ x: hoveredSquareX, y: hoveredSquareY });
};
const handleMouseLeave = () => {
setHoveredSquare(null);
};
// Event listeners
window.addEventListener("resize", resizeCanvas);
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mouseleave", handleMouseLeave);
// Initial setup
resizeCanvas();
requestRef.current = requestAnimationFrame(updateAnimation);
// Cleanup
return () => {
window.removeEventListener("resize", resizeCanvas);
canvas.removeEventListener("mousemove", handleMouseMove);
canvas.removeEventListener("mouseleave", handleMouseLeave);
if (requestRef.current) {
cancelAnimationFrame(requestRef.current);
window.removeEventListener("resize", resize);
if (moveParticlesOnHover) {
container.removeEventListener("mousemove", handleMouseMove);
}
cancelAnimationFrame(animationFrameId);
if (container.contains(gl.canvas)) {
container.removeChild(gl.canvas);
}
};
}, [
direction,
particleCount,
particleSpread,
speed,
borderColor,
hoverFillColor,
hoveredSquare,
squareSize,
moveParticlesOnHover,
particleHoverFactor,
alphaParticles,
particleBaseSize,
sizeRandomness,
cameraDistance,
disableRotation,
]);
return (
<canvas
ref={canvasRef}
className={`w-full h-full border-none block ${className}`}
/>
<div ref={containerRef} className={`relative w-full h-full ${className}`} />
);
}
};
export default Particles;
+2
View File
@@ -13,9 +13,11 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cobe": "^0.6.4",
"gsap": "^3.13.0",
"lucide-react": "^0.542.0",
"motion": "^12.23.12",
"next": "15.5.2",
"ogl": "^1.0.11",
"react": "19.1.0",
"react-dom": "19.1.0",
"tailwind-merge": "^3.3.1",
+16
View File
@@ -17,6 +17,9 @@ importers:
cobe:
specifier: ^0.6.4
version: 0.6.4
gsap:
specifier: ^3.13.0
version: 3.13.0
lucide-react:
specifier: ^0.542.0
version: 0.542.0([email protected])
@@ -26,6 +29,9 @@ importers:
next:
specifier: 15.5.2
version: 15.5.2([email protected]([email protected]))([email protected])
ogl:
specifier: ^1.0.11
version: 1.0.11
react:
specifier: 19.1.0
version: 19.1.0
@@ -483,6 +489,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
[email protected]:
resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==}
[email protected]:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
@@ -621,6 +630,9 @@ packages:
sass:
optional: true
[email protected]:
resolution: {integrity: sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==}
[email protected]:
resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==}
@@ -1036,6 +1048,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
optional: true
@@ -1141,6 +1155,8 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
[email protected]: {}
[email protected]: {}
[email protected]: {}