Halftone Flow
Halftone Flow é um componente para React e Tailwind CSS, escrito para a NedDev e teu para usares. Copia e cola no teu projeto.
NedDev
other
O que é
Halftone Flow é um componente para React e Tailwind CSS, escrito para a NedDev e teu para usares. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Quatro cópias grátis por semana. Sem cartão.
Não precisa de pacotes além do que o projeto já tem
Licença
Este componente foi escrito para este catálogo. É teu para usares em trabalho de cliente e em produtos que vendas, nos termos de uso.
Halftone Flow
import { useMemo, type CSSProperties } from "react";
type EffectMode = "dark" | "light";
export type HalftoneFlowProps = {
mode?: EffectMode;
hue?: number;
saturation?: number;
brightness?: number;
className?: string;
style?: CSSProperties;
};
const BACKGROUND = "#000000";
const TARGETS = [{ selector: "#glcanvas", role: "background" }] as const;
function clamp(value: number, minimum: number, maximum: number) {
return Math.min(maximum, Math.max(minimum, value));
}
function buildFocusedDocument(sourceHtml: string) {
const targetJson = JSON.stringify(TARGETS).replace(/</g, "\\u003c");
const focusStyle = `<style data-threeui-focus>
html, body { width: 100% !important; height: 100% !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; background: ${BACKGROUND} !important; }
body { position: relative !important; display: flex !important; align-items: center !important; justify-content: center !important; }
body > * { visibility: hidden !important; }
body[data-threeui-ready] > [data-threeui-role] { visibility: visible !important; }
[data-threeui-residual] { display: none !important; }
[data-threeui-role="background"] { position: fixed !important; inset: 0 !important; width: 100% !important; height: 100% !important; max-width: none !important; max-height: none !important; z-index: 0 !important; opacity: 1 !important; pointer-events: none !important; }
[data-threeui-role="ui"] { position: relative !important; z-index: 1 !important; width: min(calc(100% - 32px), var(--threeui-target-width, 1040px)) !important; max-width: none !important; max-height: calc(100% - 32px) !important; margin: auto !important; overflow: auto !important; opacity: 1 !important; transform: none !important; filter: none !important; flex: none !important; box-sizing: border-box !important; }
</style>`;
const focusScript = `<script data-threeui-focus>
(function () {
var isolated = false;
function isolate() {
if (isolated) return;
var specs = ${targetJson};
var roots = [];
specs.forEach(function (spec) {
var element = document.querySelector(spec.selector);
if (!element) return;
element.setAttribute('data-threeui-role', spec.role);
if (spec.width) element.style.setProperty('--threeui-target-width', spec.width);
if (!roots.some(function (root) { return root.contains(element); })) roots.push(element);
});
if (!roots.length) return;
isolated = true;
roots.forEach(function (root) { document.body.appendChild(root); });
Array.from(document.body.children).forEach(function (element) {
if (roots.indexOf(element) !== -1) return;
element.setAttribute('data-threeui-residual', '');
element.setAttribute('aria-hidden', 'true');
if ('inert' in element) element.inert = true;
});
document.body.setAttribute('data-threeui-ready', '');
requestAnimationFrame(function () { window.dispatchEvent(new Event('resize')); });
}
function scheduleIsolation() { setTimeout(isolate, 100); }
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', scheduleIsolation, { once: true });
else scheduleIsolation();
window.addEventListener('load', isolate, { once: true });
})();
</script>`;
return sourceHtml
.replace(/<\/head>/i, `${focusStyle}</head>`)
.replace(/<\/body>/i, `${focusScript}</body>`);
}
function HalftoneFlow({
mode = "dark",
hue = 0,
saturation = 1,
brightness = 1,
className,
style,
}: HalftoneFlowProps) {
const safeMode: EffectMode = mode === "light" ? "light" : "dark";
const source = useMemo(
() => buildFocusedDocument(NEXUS_UNIFIED_FLOW_HTML),
[],
);
const safeHue = clamp(hue, -180, 180);
const safeSaturation = clamp(saturation, 0, 2);
const safeBrightness = clamp(brightness, 0.35, 1.65);
const filter =
safeHue === 0 && safeSaturation === 1 && safeBrightness === 1
? undefined
: `hue-rotate(${safeHue}deg) saturate(${safeSaturation}) brightness(${safeBrightness})`;
return (
<iframe
className={className}
data-mode={safeMode}
title="Nexus unified halftone flow"
srcDoc={source}
sandbox="allow-scripts"
loading="eager"
style={{
display: "block",
width: "100%",
height: "100%",
border: 0,
background: BACKGROUND,
filter,
...style,
}}
/>
);
}
const NEXUS_UNIFIED_FLOW_HTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus - Unified Ecosystem</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<!-- GSAP for Animations -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
</head>
<body class="bg-black text-white font-sans min-h-screen overflow-x-hidden antialiased selection:bg-red-500/30 flex flex-col relative" style="font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
<!-- WebGL Background -->
<canvas id="glcanvas" class="fixed inset-0 w-full h-full -z-20"></canvas>
<!-- Overlay structural lines to match reference -->
<div class="fixed inset-0 pointer-events-none -z-10 w-full h-full">
<!-- Thin angled lines behind logo area -->
<div class="absolute top-0 left-0 w-[600px] h-[600px] border-l border-t border-white/5 origin-top-left rotate-[15deg] translate-x-[-100px] translate-y-[-100px]"></div>
<div class="absolute top-0 left-0 w-[800px] h-[800px] border-l border-white/5 origin-top-left rotate-[-25deg] translate-x-[200px] translate-y-[-50px]"></div>
<!-- Horizontal grid line -->
<div class="absolute top-[20%] w-full border-t border-white/5"></div>
</div>
<!-- Main Content Layout -->
<div class="flex-1 flex flex-col relative z-10 w-full max-w-[1600px] mx-auto min-h-screen border-l border-r border-white/10">
<!-- Hero Section -->
<main class="flex-1 flex flex-col lg:flex-row items-center lg:items-center justify-between px-8 sm:px-16 lg:px-24 py-24 lg:py-0 relative">
<!-- Left: Logo & Title -->
<div class="flex items-center gap-6 group cursor-default w-full lg:w-1/2 mb-20 lg:mb-0">
<!-- Abstract Geometric Icon -->
<svg width="72" height="72" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" class="text-white transform group-hover:scale-105 transition-transform duration-500 ease-out" style="flex-shrink: 0;">
<path d="M10 20 L50 40 L90 10 L80 50 L95 85 L50 60 L15 90 L25 50 Z" fill="currentColor"/>
<path d="M50 40 L50 60" stroke="black" stroke-width="2"/>
<path d="M10 20 L50 60" stroke="black" stroke-width="2"/>
</svg>
<!-- Masked Reveal Title -->
<h1 class="text-7xl sm:text-8xl lg:text-9xl font-semibold tracking-tighter lowercase leading-none flex flex-wrap gap-x-4 gap-y-2 reveal-text">
<span class="inline-block overflow-hidden pt-2">
<span class="reveal-word inline-block translate-y-[120%] pb-2">nexus</span>
</span>
<span class="inline-block overflow-hidden pt-2">
<span class="reveal-word inline-block translate-y-[120%] pb-2">flow</span>
</span>
</h1>
</div>
<!-- Right: Input & Social Proof -->
<div class="w-full lg:w-1/2 flex flex-col items-start lg:items-end relative">
<!-- Offset Input Container -->
<div class="relative group w-full max-w-md">
<!-- Stylized Shadow/Offset with subtle gradient -->
<div class="absolute inset-0 bg-gradient-to-br from-[#A63A29] to-[#732215] translate-x-3 translate-y-3 transition-transform duration-300 ease-out group-hover:translate-x-4 group-hover:translate-y-4"></div>
<!-- Input Box with Premium Gradient Border Treatment -->
<div class="relative z-10 p-[1px] bg-gradient-to-r from-white/40 via-white/10 to-white/30">
<div class="bg-white flex items-center justify-between p-1 w-full h-[60px] sm:h-[72px]">
<input type="email" placeholder="Join the waitlist..." class="w-full h-full bg-transparent text-black outline-none px-6 text-base sm:text-lg font-medium placeholder:text-gray-500 placeholder:font-normal" />
</div>
</div>
</div>
<!-- Users Proof -->
<div class="flex flex-row items-center gap-4 mt-10 mr-2">
<span class="text-sm font-medium tracking-wide">5,102+ early adopters</span>
<div class="flex -space-x-3">
<!-- Avatars with Gradient Borders -->
<div class="w-8 h-8 rounded-full p-[2px] bg-gradient-to-br from-white/50 to-white/10 bg-black shadow-sm">
<img class="w-full h-full rounded-full object-cover" src="https://cdn.21st.dev/assets/localized/868ee7e6983767d120387ecbd017fd6870f85aa0c5aeba6db06d54e0504c2934.jpg" alt="User avatar">
</div>
<div class="w-8 h-8 rounded-full p-[2px] bg-gradient-to-br from-white/50 to-white/10 bg-black shadow-sm">
<img class="w-full h-full rounded-full object-cover" src="https://cdn.21st.dev/assets/localized/95767b6c354c4ac206cadaac8937b5a7b74ea1e227fa2f578703674d3725bcdd.jpg" alt="User avatar">
</div>
<div class="w-8 h-8 rounded-full p-[2px] bg-gradient-to-br from-white/50 to-white/10 bg-black shadow-sm">
<img class="w-full h-full rounded-full object-cover" src="https://cdn.21st.dev/assets/localized/4526a25eb7c0b86d945cf5e43ed53e85be9821edb8dc26017ed372b1450ae6f6.jpg" alt="User avatar">
</div>
<div class="w-8 h-8 rounded-full p-[2px] bg-gradient-to-br from-white/50 to-white/10 bg-black shadow-sm">
<img class="w-full h-full rounded-full object-cover" src="https://cdn.21st.dev/assets/localized/3c53a7cdf511b5078b720e0846641af3f3627ccd5ad310dc21346f24e8cc7fb5.jpg" alt="User avatar">
</div>
</div>
</div>
</div>
</main>
<!-- Structural Horizontal Divider -->
<div class="w-full border-t border-white/20"></div>
<!-- Footer Section -->
<footer class="w-full grid grid-cols-1 lg:grid-cols-12 min-h-[300px]">
<!-- Footer Left: Brand & Desc -->
<div class="lg:col-span-4 p-8 sm:p-12 lg:p-16 border-b lg:border-b-0 lg:border-r border-white/20 flex flex-col gap-6">
<div class="flex items-center gap-3">
<svg width="24" height="24" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" class="text-white">
<path d="M10 20 L50 40 L90 10 L80 50 L95 85 L50 60 L15 90 L25 50 Z" fill="currentColor"/>
</svg>
<span class="text-base font-semibold tracking-tight">Nexus</span>
</div>
<p class="text-sm text-gray-400 leading-relaxed max-w-[280px]">
Your unified ecosystem to synchronize thoughts, maintain momentum, and build effortlessly.
</p>
</div>
<!-- Footer Right: Links -->
<div class="lg:col-span-8 p-8 sm:p-12 lg:p-16 grid grid-cols-2 sm:grid-cols-3 gap-12 sm:gap-8">
<!-- Column 1 -->
<div class="flex flex-col gap-5">
<h3 class="text-sm font-semibold tracking-tight text-white mb-1">Product</h3>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Capabilities</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Mechanism</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Plans</a>
</div>
<!-- Column 2 -->
<div class="flex flex-col gap-5">
<h3 class="text-sm font-semibold tracking-tight text-white mb-1">Company</h3>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Our Story</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Join Us</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Get in Touch</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Media</a>
</div>
<!-- Column 3 -->
<div class="flex flex-col gap-5">
<h3 class="text-sm font-semibold tracking-tight text-white mb-1">Resources</h3>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Support</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Changelog</a>
<a href="#" class="text-sm text-gray-400 hover:text-white transition-colors duration-200">Documentation</a>
</div>
</div>
</footer>
</div>
<!-- Scripts -->
<script>
// --- WebGL Background Animation ---
const canvas = document.getElementById('glcanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL not supported');
} else {
// Resize handler
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();
// Vertex Shader
const vsSource = \`
attribute vec4 aVertexPosition;
void main() {
gl_Position = aVertexPosition;
}
\`;
// Fragment Shader (Halftone Flow)
const fsSource = \`
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
mat2 rot(float a) {
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c);
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec2 p = uv * 2.0 - 1.0;
p.x *= u_resolution.x / u_resolution.y;
vec2 flow_uv = p;
float time = u_time * 0.4;
for(float i = 1.0; i < 4.0; i++) {
flow_uv *= rot(time * 0.1);
flow_uv.x += sin(flow_uv.y * 2.0 * i + time) * 0.5;
flow_uv.y += cos(flow_uv.x * 1.5 * i - time * 0.8) * 0.5;
}
float intensity = sin(flow_uv.x * 2.0 + flow_uv.y * 3.0) * 0.5 + 0.5;
vec3 col_dark = vec3(0.02, 0.0, 0.0);
vec3 col_red = vec3(0.8, 0.1, 0.05);
vec3 col_bright = vec3(1.0, 0.6, 0.2);
vec3 fluid_color = mix(col_dark, col_red, smoothstep(0.2, 0.6, intensity));
fluid_color = mix(fluid_color, col_bright, smoothstep(0.7, 1.0, intensity));
float gridSize = 6.0;
vec2 grid_uv = gl_FragCoord.xy / gridSize;
vec2 cell_uv = fract(grid_uv) - 0.5;
float dist = length(cell_uv);
float radius = intensity * 0.45;
float dot_mask = smoothstep(radius, radius - 0.1, dist);
vec3 final_color = mix(vec3(0.0), fluid_color, dot_mask);
final_color += fluid_color * 0.15;
gl_FragColor = vec4(final_color, 1.0);
}
\`;
function compileShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const positions = new Float32Array([
-1.0, 1.0,
1.0, 1.0,
-1.0, -1.0,
1.0, -1.0,
]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
const timeLocation = gl.getUniformLocation(program, "u_time");
let startTime = Date.now();
function render() {
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
gl.uniform1f(timeLocation, (Date.now() - startTime) / 1000.0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(render);
}
render();
}
// --- GSAP ScrollTrigger Masked Word Reveal ---
document.addEventListener("DOMContentLoaded", (event) => {
gsap.registerPlugin(ScrollTrigger);
gsap.to(".reveal-word", {
y: "0%",
duration: 1.2,
stagger: 0.15,
ease: "power4.out",
scrollTrigger: {
trigger: ".reveal-text",
start: "top 90%",
}
});
});
</script>
</body>
</html>`;
export default HalftoneFlow;
export { HalftoneFlow };