fix(ci): lint do Biome, Next 16.3.8 (RCE em next/og) e transicoes que se remedem
CI / verify (push) Failing after 5m43s
CI / verify (push) Failing after 5m43s
- Biome: formatacao, ordem de imports, max-width duplicado no cabecalho de secao, supressao justificada para nomes de view-transition, ajustes nos scripts de auditoria de SEO - next 16.3.3 -> 16.3.8: GHSA-vcvr-r3jv-pc5j (execucao remota de codigo em next/og ImageResponse, usado nas imagens de compartilhamento) - overrides de brace-expansion (GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p, GHSA-q2hr-2g5m-vwhr), dependencia de desenvolvimento via jest - visibilidade das cenas le as posicoes a cada quadro e o GSAP remede as passagens quando a pagina muda de altura (fontes, imagens, Experiencia) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6db10ffa67
commit
4c826e938f
17 files changed
+816
-253
No files matched your search
@@ -42,7 +42,13 @@ class FieldBuilder {
|
||||
alpha: number[] = [];
|
||||
constructor(private density: number) {}
|
||||
|
||||
point(x: number, y: number, z: number, style: PointStyle = {}, to?: [number, number, number]) {
|
||||
point(
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
style: PointStyle = {},
|
||||
to?: [number, number, number],
|
||||
) {
|
||||
this.base.push(x, y, z);
|
||||
this.to.push(...(to ?? [x, y, z]));
|
||||
this.move.push(to ? 1 : 0);
|
||||
@@ -50,36 +56,89 @@ class FieldBuilder {
|
||||
this.size.push(style.size ?? 0.02);
|
||||
this.alpha.push(style.alpha ?? 0.7);
|
||||
}
|
||||
line(y: Layer, x0: number, z0: number, x1: number, z1: number, step: number, style?: PointStyle) {
|
||||
line(
|
||||
y: Layer,
|
||||
x0: number,
|
||||
z0: number,
|
||||
x1: number,
|
||||
z1: number,
|
||||
step: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
const s = step * this.density;
|
||||
const n = Math.max(1, Math.round(Math.hypot(x1 - x0, z1 - z0) / s));
|
||||
for (let i = 0; i <= n; i++) this.point(x0 + ((x1 - x0) * i) / n, y, z0 + ((z1 - z0) * i) / n, style);
|
||||
for (let i = 0; i <= n; i++)
|
||||
this.point(x0 + ((x1 - x0) * i) / n, y, z0 + ((z1 - z0) * i) / n, style);
|
||||
}
|
||||
rect(y: Layer, x0: number, z0: number, x1: number, z1: number, step: number, style?: PointStyle) {
|
||||
rect(
|
||||
y: Layer,
|
||||
x0: number,
|
||||
z0: number,
|
||||
x1: number,
|
||||
z1: number,
|
||||
step: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
this.line(y, x0, z0, x1, z0, step, style);
|
||||
this.line(y, x1, z0, x1, z1, step, style);
|
||||
this.line(y, x1, z1, x0, z1, step, style);
|
||||
this.line(y, x0, z1, x0, z0, step, style);
|
||||
}
|
||||
fill(y: Layer, x0: number, z0: number, x1: number, z1: number, step: number, style?: PointStyle) {
|
||||
fill(
|
||||
y: Layer,
|
||||
x0: number,
|
||||
z0: number,
|
||||
x1: number,
|
||||
z1: number,
|
||||
step: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
const s = step * this.density;
|
||||
for (let x = x0; x <= x1 + 1e-6; x += s)
|
||||
for (let z = z0; z <= z1 + 1e-6; z += s)
|
||||
this.point(x + (Math.random() - 0.5) * s * 0.3, y, z + (Math.random() - 0.5) * s * 0.3, style);
|
||||
this.point(
|
||||
x + (Math.random() - 0.5) * s * 0.3,
|
||||
y,
|
||||
z + (Math.random() - 0.5) * s * 0.3,
|
||||
style,
|
||||
);
|
||||
}
|
||||
ring(y: Layer, cx: number, cz: number, r: number, step: number, style?: PointStyle) {
|
||||
const n = Math.max(8, Math.round((Math.PI * 2 * r) / (step * this.density)));
|
||||
ring(
|
||||
y: Layer,
|
||||
cx: number,
|
||||
cz: number,
|
||||
r: number,
|
||||
step: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
const n = Math.max(
|
||||
8,
|
||||
Math.round((Math.PI * 2 * r) / (step * this.density)),
|
||||
);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = (i / n) * Math.PI * 2;
|
||||
this.point(cx + Math.cos(a) * r, y, cz + Math.sin(a) * r, style);
|
||||
}
|
||||
}
|
||||
disc(y: Layer, cx: number, cz: number, r: number, step: number, style?: PointStyle) {
|
||||
for (let rr = r; rr > 0.005; rr -= step * this.density) this.ring(y, cx, cz, rr, step, style);
|
||||
disc(
|
||||
y: Layer,
|
||||
cx: number,
|
||||
cz: number,
|
||||
r: number,
|
||||
step: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
for (let rr = r; rr > 0.005; rr -= step * this.density)
|
||||
this.ring(y, cx, cz, rr, step, style);
|
||||
this.point(cx, y, cz, style);
|
||||
}
|
||||
/** partículas que correm de a até b (a posição é calculada no shader) */
|
||||
flow(a: [number, number, number], b: [number, number, number], count: number, style?: PointStyle) {
|
||||
flow(
|
||||
a: [number, number, number],
|
||||
b: [number, number, number],
|
||||
count: number,
|
||||
style?: PointStyle,
|
||||
) {
|
||||
const n = Math.max(2, Math.round(count / this.density));
|
||||
for (let i = 0; i < n; i++) this.point(...a, style, b);
|
||||
}
|
||||
@@ -91,12 +150,17 @@ function drawScreen(f: FieldBuilder) {
|
||||
f.fill(y, -2.1, -1.4, 2.1, 1.4, 0.1, { alpha: 0.1 }); // superfície da tela
|
||||
f.rect(y, -2.1, -1.4, 2.1, 1.4, 0.026, { alpha: 0.95 });
|
||||
f.line(y, -2.1, -1.05, 2.1, -1.05, 0.03, { alpha: 0.6 }); // barra do navegador
|
||||
for (const x of [-1.9, -1.72, -1.54]) f.disc(y, x, -1.23, 0.05, 0.025, { alpha: 0.8 });
|
||||
for (const x of [-1.9, -1.72, -1.54])
|
||||
f.disc(y, x, -1.23, 0.05, 0.025, { alpha: 0.8 });
|
||||
f.rect(y, -1.1, -1.32, 1.3, -1.14, 0.035, { alpha: 0.45 }); // endereço
|
||||
f.fill(y, -1.8, -0.82, 0.25, -0.58, 0.03, { alpha: 0.85 }); // título
|
||||
f.line(y, -1.8, -0.38, 0.75, -0.38, 0.04, { alpha: 0.5 }); // texto
|
||||
f.line(y, -1.8, -0.24, 0.35, -0.24, 0.04, { alpha: 0.5 });
|
||||
f.fill(y, -1.8, -0.02, -0.75, 0.2, 0.028, { tone: 1, alpha: 0.95, size: 0.022 }); // botão
|
||||
f.fill(y, -1.8, -0.02, -0.75, 0.2, 0.028, {
|
||||
tone: 1,
|
||||
alpha: 0.95,
|
||||
size: 0.022,
|
||||
}); // botão
|
||||
f.rect(y, 0.6, -0.82, 1.8, 0.2, 0.035, { alpha: 0.6 }); // imagem
|
||||
f.fill(y, 0.6, -0.82, 1.8, 0.2, 0.08, { alpha: 0.25 });
|
||||
for (const [x0, x1] of [
|
||||
@@ -127,9 +191,18 @@ function drawIntegrations(f: FieldBuilder) {
|
||||
const ux = x / len;
|
||||
const uz = z / len;
|
||||
// linha da central até o sistema, e mensagens correndo nos dois sentidos
|
||||
f.line(y, ux * 0.38, uz * 0.38, x - ux * 0.24, z - uz * 0.24, 0.06, { alpha: 0.35 });
|
||||
f.flow([ux * 0.38, y, uz * 0.38], [x - ux * 0.24, y, z - uz * 0.24], 10, { tone: 1, alpha: 0.95, size: 0.026 });
|
||||
f.flow([x - ux * 0.24, y, z - uz * 0.24], [ux * 0.38, y, uz * 0.38], 6, { alpha: 0.9, size: 0.024 });
|
||||
f.line(y, ux * 0.38, uz * 0.38, x - ux * 0.24, z - uz * 0.24, 0.06, {
|
||||
alpha: 0.35,
|
||||
});
|
||||
f.flow([ux * 0.38, y, uz * 0.38], [x - ux * 0.24, y, z - uz * 0.24], 10, {
|
||||
tone: 1,
|
||||
alpha: 0.95,
|
||||
size: 0.026,
|
||||
});
|
||||
f.flow([x - ux * 0.24, y, z - uz * 0.24], [ux * 0.38, y, uz * 0.38], 6, {
|
||||
alpha: 0.9,
|
||||
size: 0.024,
|
||||
});
|
||||
const [nx, nz] = NODES[(k + 1) % NODES.length];
|
||||
f.line(y, x, z, nx, nz, 0.08, { alpha: 0.15 });
|
||||
});
|
||||
@@ -144,12 +217,15 @@ function drawData(f: FieldBuilder) {
|
||||
const cols = [x0, -0.9, 0.2, 1.1, x1];
|
||||
f.rect(y, x0, z0, x1, z1, 0.03, { alpha: 0.8 });
|
||||
f.fill(y, x0, z0, x1, z0 + 0.32, 0.045, { tone: 0.6, alpha: 0.45 }); // cabeçalho da tabela
|
||||
for (let z = z0 + 0.32; z < z1; z += 0.33) f.line(y, x0, z, x1, z, 0.04, { alpha: 0.4 });
|
||||
for (const x of cols.slice(1, -1)) f.line(y, x, z0, x, z1, 0.04, { alpha: 0.4 });
|
||||
for (let z = z0 + 0.32; z < z1; z += 0.33)
|
||||
f.line(y, x0, z, x1, z, 0.04, { alpha: 0.4 });
|
||||
for (const x of cols.slice(1, -1))
|
||||
f.line(y, x, z0, x, z1, 0.04, { alpha: 0.4 });
|
||||
for (let z = z0 + 0.48; z < z1; z += 0.33) {
|
||||
for (let c = 0; c < cols.length - 1; c++) {
|
||||
const start = cols[c] + 0.12;
|
||||
const width = (cols[c + 1] - cols[c] - 0.24) * (0.35 + Math.random() * 0.6);
|
||||
const width =
|
||||
(cols[c + 1] - cols[c] - 0.24) * (0.35 + Math.random() * 0.6);
|
||||
f.line(y, start, z, start + width, z, 0.045, { alpha: 0.55 }); // valores nas células
|
||||
}
|
||||
}
|
||||
@@ -173,10 +249,16 @@ function buildField(dense: boolean) {
|
||||
const s = Math.sqrt(1 - u * u);
|
||||
const d = 4 + Math.random() * 7;
|
||||
scatter.set([Math.cos(t) * s * d, u * d, Math.sin(t) * s * d], n * 3);
|
||||
rand.set([Math.random(), Math.random(), Math.random(), Math.random()], n * 4);
|
||||
rand.set(
|
||||
[Math.random(), Math.random(), Math.random(), Math.random()],
|
||||
n * 4,
|
||||
);
|
||||
}
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute("position", new THREE.Float32BufferAttribute(f.base, 3));
|
||||
geometry.setAttribute(
|
||||
"position",
|
||||
new THREE.Float32BufferAttribute(f.base, 3),
|
||||
);
|
||||
geometry.setAttribute("aTo", new THREE.Float32BufferAttribute(f.to, 3));
|
||||
geometry.setAttribute("aMove", new THREE.Float32BufferAttribute(f.move, 1));
|
||||
geometry.setAttribute("aTone", new THREE.Float32BufferAttribute(f.tone, 1));
|
||||
@@ -314,7 +396,12 @@ const LABEL_ANCHORS: [number, Layer, number][] = [
|
||||
[2, -1, 1.3],
|
||||
];
|
||||
|
||||
function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps) {
|
||||
function Field({
|
||||
progressRef,
|
||||
labelsRef,
|
||||
onReady,
|
||||
onError,
|
||||
}: HeroFieldSceneProps) {
|
||||
const { gl, camera, size } = useThree();
|
||||
const group = useRef<THREE.Group>(null);
|
||||
const dense = useMemo(
|
||||
@@ -397,11 +484,14 @@ function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps
|
||||
const time = frame.clock.elapsedTime;
|
||||
|
||||
// rolagem amortecida: o mergulho segue o dedo com um leve atraso de câmera
|
||||
s.progress += ((progressRef.current ?? 0) - s.progress) * (1 - Math.exp(-dt * 5));
|
||||
s.progress +=
|
||||
((progressRef.current ?? 0) - s.progress) * (1 - Math.exp(-dt * 5));
|
||||
const p = s.progress;
|
||||
const aspect = size.width / size.height;
|
||||
const wide = aspect > 1.1;
|
||||
const tanHalf = Math.tan(THREE.MathUtils.degToRad((camera as THREE.PerspectiveCamera).fov / 2));
|
||||
const tanHalf = Math.tan(
|
||||
THREE.MathUtils.degToRad((camera as THREE.PerspectiveCamera).fov / 2),
|
||||
);
|
||||
// enquadramento pela largura: a camada (4,2 de largura) com margem cabe na tela, inclusive em pé
|
||||
const frameDist = Math.max(FRAME, 2.35 / (tanHalf * aspect));
|
||||
const spreadMax = Math.max(SPREAD, frameDist + 0.6);
|
||||
@@ -416,7 +506,8 @@ function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps
|
||||
u.uFade.value = 1 - smooth(0.93, 1, p);
|
||||
// com a câmera mais longe (celular), os pontos crescem um pouco para o desenho não sumir
|
||||
u.uScale.value =
|
||||
((gl.getPixelRatio() * size.height * 0.5) / tanHalf) * Math.sqrt(frameDist / FRAME);
|
||||
((gl.getPixelRatio() * size.height * 0.5) / tanHalf) *
|
||||
Math.sqrt(frameDist / FRAME);
|
||||
|
||||
// sem mouse (toque, ou mouse parado), um cursor fantasma percorre a tela
|
||||
const mouseActive = time - s.lastMove < 2.5 && s.lastMove > 0 ? 1 : 0;
|
||||
@@ -425,16 +516,22 @@ function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps
|
||||
// em repouso, vista de três quartos que balança devagar; ao mergulhar, as camadas ficam de frente e retas
|
||||
g.rotation.set(
|
||||
0.42 + (Math.PI / 2 - 0.42) * face - s.tilt.y * 0.08,
|
||||
(-0.45 + Math.sin(time * 0.2) * 0.08) * (1 - face) + s.tilt.x * 0.15 * (1 - face),
|
||||
(-0.45 + Math.sin(time * 0.2) * 0.08) * (1 - face) +
|
||||
s.tilt.x * 0.15 * (1 - face),
|
||||
0,
|
||||
);
|
||||
const settle = 1 - smooth(0, 0.28, p);
|
||||
// em repouso: à direita do texto na tela larga; no celular, mais abaixo, atrás dos botões
|
||||
g.position.set(wide ? 1.7 * settle : 0, (wide ? -0.1 : -rest * tanHalf * 0.38) * settle, 0);
|
||||
g.position.set(
|
||||
wide ? 1.7 * settle : 0,
|
||||
(wide ? -0.1 : -rest * tanHalf * 0.38) * settle,
|
||||
0,
|
||||
);
|
||||
camera.position.z = keyframes(cameraFrames(rest, spreadMax, frameDist), p);
|
||||
// foco na próxima camada à frente da câmera (as de trás desfocam como em uma lente)
|
||||
const spread = u.uSpread.value;
|
||||
const ahead = [spread, 0, -spread].find((z) => z < camera.position.z - 0.2) ?? -spread;
|
||||
const ahead =
|
||||
[spread, 0, -spread].find((z) => z < camera.position.z - 0.2) ?? -spread;
|
||||
u.uFocus.value = Math.max(1.5, camera.position.z - ahead);
|
||||
g.updateMatrixWorld();
|
||||
|
||||
@@ -445,17 +542,24 @@ function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps
|
||||
.applyMatrix4(g.matrixWorld);
|
||||
if (helpers.ray.ray.intersectPlane(helpers.plane, helpers.hit)) {
|
||||
g.worldToLocal(helpers.hit);
|
||||
s.mouse.lerp(helpers.target.set(helpers.hit.x, helpers.hit.z), 1 - Math.exp(-dt * 10));
|
||||
s.mouse.lerp(
|
||||
helpers.target.set(helpers.hit.x, helpers.hit.z),
|
||||
1 - Math.exp(-dt * 10),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
s.mouse.lerp(
|
||||
helpers.target.set(Math.sin(time * 0.31) * 1.5, Math.sin(time * 0.23 + 1.3) * 1.1),
|
||||
helpers.target.set(
|
||||
Math.sin(time * 0.31) * 1.5,
|
||||
Math.sin(time * 0.23 + 1.3) * 1.1,
|
||||
),
|
||||
1 - Math.exp(-dt * 1.5),
|
||||
);
|
||||
}
|
||||
u.uMouse.value.copy(s.mouse);
|
||||
const targetAmt = (mouseActive ? 0.8 : 0.4) * (1 - smooth(0.05, 0.3, p));
|
||||
u.uMouseAmt.value += (targetAmt - u.uMouseAmt.value) * (1 - Math.exp(-dt * 4));
|
||||
u.uMouseAmt.value +=
|
||||
(targetAmt - u.uMouseAmt.value) * (1 - Math.exp(-dt * 4));
|
||||
|
||||
// etiquetas: projeta o canto de cada camada na tela e as esconde quando o mergulho começa
|
||||
const labels = labelsRef.current ?? [];
|
||||
@@ -463,7 +567,10 @@ function Field({ progressRef, labelsRef, onReady, onError }: HeroFieldSceneProps
|
||||
LABEL_ANCHORS.forEach(([x, layer, z], index) => {
|
||||
const label = labels[index];
|
||||
if (!label) return;
|
||||
helpers.anchor.set(x, layer * u.uSpread.value, z).applyMatrix4(g.matrixWorld).project(camera);
|
||||
helpers.anchor
|
||||
.set(x, layer * u.uSpread.value, z)
|
||||
.applyMatrix4(g.matrixWorld)
|
||||
.project(camera);
|
||||
const px = ((helpers.anchor.x + 1) / 2) * size.width;
|
||||
const py = ((1 - helpers.anchor.y) / 2) * size.height;
|
||||
label.style.transform = `translate3d(${px.toFixed(1)}px, ${py.toFixed(1)}px, 0)`;
|
||||
@@ -505,7 +612,11 @@ export default function HeroFieldScene(props: HeroFieldSceneProps) {
|
||||
camera={{ fov: 35, position: [0, 0, 9], near: 0.05, far: 120 }}
|
||||
dpr={[1, 1.75]}
|
||||
frameloop={props.visible ? "always" : "never"}
|
||||
gl={{ alpha: true, antialias: false, powerPreference: "high-performance" }}
|
||||
gl={{
|
||||
alpha: true,
|
||||
antialias: false,
|
||||
powerPreference: "high-performance",
|
||||
}}
|
||||
>
|
||||
<Field {...props} />
|
||||
</Canvas>
|
||||
|
||||
Reference in new issue
Block a user