cleanup
This commit is contained in:
@@ -9,12 +9,9 @@ interface CTABannerSectionProps {
|
||||
|
||||
export default function CTABannerSection(props: CTABannerSectionProps) {
|
||||
return (
|
||||
<section
|
||||
id="cta"
|
||||
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
|
||||
>
|
||||
<section id="cta" class={cn("py-20 md:py-28 scroll-mt-16", props.class)}>
|
||||
<PageContainer py="py-8">
|
||||
<div class="gradient-card border border-[var(--color-border)]/50 rounded-2xl p-10 md:p-16 text-center">
|
||||
<div class="gradient-card border border-(--color-border)/50 rounded-2xl p-10 md:p-16 text-center">
|
||||
<h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[var(--color-text-primary)] mb-4">
|
||||
Ready to protect your identity?
|
||||
</h2>
|
||||
|
||||
@@ -1,119 +1,488 @@
|
||||
import { onMount, onCleanup } from "solid-js";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { onMount, onCleanup, splitProps } from "solid-js";
|
||||
|
||||
interface ColorWaveBackgroundProps {
|
||||
class?: string;
|
||||
const VERTEX_SHADER = `
|
||||
precision highp float;
|
||||
|
||||
float mod289(float x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
||||
|
||||
float permute(float x) { return mod289(((x * 34.0) + 1.0) * x); }
|
||||
vec2 permute(vec2 x) { return mod289(((x * 34.0) + 1.0) * x); }
|
||||
vec3 permute(vec3 x) { return mod289(((x * 34.0) + 1.0) * x); }
|
||||
vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
|
||||
|
||||
float psrdnoise(vec2 x, vec2 period, float alpha, out vec2 gradient) {
|
||||
vec2 uv = vec2(x.x + x.y * 0.5, x.y);
|
||||
vec2 i0 = floor(uv);
|
||||
vec2 f0 = fract(uv);
|
||||
float cmp = step(f0.y, f0.x);
|
||||
vec2 o1 = vec2(cmp, 1.0 - cmp);
|
||||
vec2 i1 = i0 + o1;
|
||||
vec2 i2 = i0 + vec2(1.0, 1.0);
|
||||
vec2 v0 = vec2(i0.x - i0.y * 0.5, i0.y);
|
||||
vec2 v1 = vec2(v0.x + o1.x - o1.y * 0.5, v0.y + o1.y);
|
||||
vec2 v2 = vec2(v0.x + 0.5, v0.y + 1.0);
|
||||
vec2 x0 = x - v0;
|
||||
vec2 x1 = x - v1;
|
||||
vec2 x2 = x - v2;
|
||||
vec3 iu, iv;
|
||||
vec3 xw, yw;
|
||||
if(any(greaterThan(period, vec2(0.0)))) {
|
||||
xw = vec3(v0.x, v1.x, v2.x);
|
||||
yw = vec3(v0.y, v1.y, v2.y);
|
||||
if(period.x > 0.0) xw = mod(vec3(v0.x, v1.x, v2.x), period.x);
|
||||
if(period.y > 0.0) yw = mod(vec3(v0.y, v1.y, v2.y), period.y);
|
||||
iu = floor(xw + 0.5 * yw + 0.5);
|
||||
iv = floor(yw + 0.5);
|
||||
} else {
|
||||
iu = vec3(i0.x, i1.x, i2.x);
|
||||
iv = vec3(i0.y, i1.y, i2.y);
|
||||
}
|
||||
vec3 hash = mod(iu, 289.0);
|
||||
hash = mod((hash * 51.0 + 2.0) * hash + iv, 289.0);
|
||||
hash = mod((hash * 34.0 + 10.0) * hash, 289.0);
|
||||
vec3 psi = hash * 0.07482 + alpha;
|
||||
vec3 gx = cos(psi);
|
||||
vec3 gy = sin(psi);
|
||||
vec2 g0 = vec2(gx.x, gy.x);
|
||||
vec2 g1 = vec2(gx.y, gy.y);
|
||||
vec2 g2 = vec2(gx.z, gy.z);
|
||||
vec3 w = 0.8 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2));
|
||||
w = max(w, 0.0);
|
||||
vec3 w2 = w * w;
|
||||
vec3 w4 = w2 * w2;
|
||||
vec3 gdotx = vec3(dot(g0, x0), dot(g1, x1), dot(g2, x2));
|
||||
float n = dot(w4, gdotx);
|
||||
vec3 w3 = w2 * w;
|
||||
vec3 dw = -8.0 * w3 * gdotx;
|
||||
vec2 dn0 = w4.x * g0 + dw.x * x0;
|
||||
vec2 dn1 = w4.y * g1 + dw.y * x1;
|
||||
vec2 dn2 = w4.z * g2 + dw.z * x2;
|
||||
gradient = 10.9 * (dn0 + dn1 + dn2);
|
||||
return 10.9 * n;
|
||||
}
|
||||
|
||||
float psrdnoise(vec3 x, vec3 period, float alpha, out vec3 gradient) {
|
||||
const mat3 M = mat3(0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0);
|
||||
const mat3 Mi = mat3(-0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5);
|
||||
vec3 uvw = M * x;
|
||||
vec3 i0 = floor(uvw);
|
||||
vec3 f0 = fract(uvw);
|
||||
vec3 g_ = step(f0.xyx, f0.yzz);
|
||||
vec3 l_ = 1.0 - g_;
|
||||
vec3 g = vec3(l_.z, g_.xy);
|
||||
vec3 l = vec3(l_.xy, g_.z);
|
||||
vec3 o1 = min(g, l);
|
||||
vec3 o2 = max(g, l);
|
||||
vec3 i1 = i0 + o1;
|
||||
vec3 i2 = i0 + o2;
|
||||
vec3 i3 = i0 + vec3(1.0);
|
||||
vec3 v0 = Mi * i0;
|
||||
vec3 v1 = Mi * i1;
|
||||
vec3 v2 = Mi * i2;
|
||||
vec3 v3 = Mi * i3;
|
||||
vec3 x0 = x - v0;
|
||||
vec3 x1 = x - v1;
|
||||
vec3 x2 = x - v2;
|
||||
vec3 x3 = x - v3;
|
||||
if(any(greaterThan(period, vec3(0.0)))) {
|
||||
vec4 vx = vec4(v0.x, v1.x, v2.x, v3.x);
|
||||
vec4 vy = vec4(v0.y, v1.y, v2.y, v3.y);
|
||||
vec4 vz = vec4(v0.z, v1.z, v2.z, v3.z);
|
||||
if(period.x > 0.0) vx = mod(vx, period.x);
|
||||
if(period.y > 0.0) vy = mod(vy, period.y);
|
||||
if(period.z > 0.0) vz = mod(vz, period.z);
|
||||
i0 = M * vec3(vx.x, vy.x, vz.x);
|
||||
i1 = M * vec3(vx.y, vy.y, vz.y);
|
||||
i2 = M * vec3(vx.z, vy.z, vz.z);
|
||||
i3 = M * vec3(vx.w, vy.w, vz.w);
|
||||
i0 = floor(i0 + 0.5);
|
||||
i1 = floor(i1 + 0.5);
|
||||
i2 = floor(i2 + 0.5);
|
||||
i3 = floor(i3 + 0.5);
|
||||
}
|
||||
vec4 hash = permute(permute(permute(vec4(i0.z, i1.z, i2.z, i3.z)) + vec4(i0.y, i1.y, i2.y, i3.y)) + vec4(i0.x, i1.x, i2.x, i3.x));
|
||||
vec4 theta = hash * 3.883222077;
|
||||
vec4 sz = hash * -0.006920415 + 0.996539792;
|
||||
vec4 psi = hash * 0.108705628;
|
||||
vec4 Ct = cos(theta);
|
||||
vec4 St = sin(theta);
|
||||
vec4 sz_prime = sqrt(1.0 - sz * sz);
|
||||
vec4 gx, gy, gz;
|
||||
if(alpha != 0.0) {
|
||||
vec4 Sp = sin(psi);
|
||||
vec4 Cp = cos(psi);
|
||||
vec4 px = Ct * sz_prime;
|
||||
vec4 py = St * sz_prime;
|
||||
vec4 pz = sz;
|
||||
vec4 Ctp = St * Sp - Ct * Cp;
|
||||
vec4 qx = mix(Ctp * St, Sp, sz);
|
||||
vec4 qy = mix(-Ctp * Ct, Cp, sz);
|
||||
vec4 qz = -(py * Cp + px * Sp);
|
||||
vec4 Sa = vec4(sin(alpha));
|
||||
vec4 Ca = vec4(cos(alpha));
|
||||
gx = Ca * px + Sa * qx;
|
||||
gy = Ca * py + Sa * qy;
|
||||
gz = Ca * pz + Sa * qz;
|
||||
} else {
|
||||
gx = Ct * sz_prime;
|
||||
gy = St * sz_prime;
|
||||
gz = sz;
|
||||
}
|
||||
vec3 g0 = vec3(gx.x, gy.x, gz.x);
|
||||
vec3 g1 = vec3(gx.y, gy.y, gz.y);
|
||||
vec3 g2 = vec3(gx.z, gy.z, gz.z);
|
||||
vec3 g3 = vec3(gx.w, gy.w, gz.w);
|
||||
vec4 w = 0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3));
|
||||
w = max(w, 0.0);
|
||||
vec4 w2 = w * w;
|
||||
vec4 w3 = w2 * w;
|
||||
vec4 gdotx = vec4(dot(g0, x0), dot(g1, x1), dot(g2, x2), dot(g3, x3));
|
||||
float n = dot(w3, gdotx);
|
||||
vec4 dw = -6.0 * w2 * gdotx;
|
||||
vec3 dn0 = w3.x * g0 + dw.x * x0;
|
||||
vec3 dn1 = w3.y * g1 + dw.y * x1;
|
||||
vec3 dn2 = w3.z * g2 + dw.z * x2;
|
||||
vec3 dn3 = w3.w * g3 + dw.w * x3;
|
||||
gradient = 39.5 * (dn0 + dn1 + dn2 + dn3);
|
||||
return 39.5 * n;
|
||||
}
|
||||
|
||||
attribute vec3 tangent;
|
||||
|
||||
uniform vec3 u_wave;
|
||||
uniform float u_scale;
|
||||
|
||||
varying vec2 v_texcoord;
|
||||
varying vec4 v_noise;
|
||||
varying vec3 v_worldPosition;
|
||||
varying vec3 v_viewNormal;
|
||||
|
||||
float getNoise(vec3 pos, float time, out vec3 gradient) {
|
||||
return psrdnoise(pos, vec3(0.0), time, gradient);
|
||||
}
|
||||
|
||||
vec3 displace(vec3 pos, float time) {
|
||||
float nearCut = step(pos.x, 0.2499);
|
||||
float waveAmpMul = 1.2 + u_wave.y;
|
||||
float scaleAmt = u_scale * 0.36;
|
||||
|
||||
vec3 gradient = vec3(0.0);
|
||||
|
||||
vec3 posOffset1 = vec3(0.0, time * 0.12, 0.0);
|
||||
float t1 = time * 0.24 + 11.111;
|
||||
float n1 = getNoise(pos * 2.2 + posOffset1, t1, gradient);
|
||||
pos.z += n1 * scaleAmt * waveAmpMul;
|
||||
|
||||
float n2 = getNoise(pos * vec3(42.0, 0.0, 0.0), 0.0, gradient);
|
||||
pos.z += n2 * 0.016 * nearCut;
|
||||
v_noise.y = n2;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 pos = position;
|
||||
vec3 calcNormal = normal;
|
||||
v_noise = vec4(0.0);
|
||||
|
||||
float waveTime = u_wave.x;
|
||||
pos = displace(pos, waveTime);
|
||||
|
||||
vec3 gradient = vec3(0.0);
|
||||
float n3 = getNoise(position * vec3(2.4, 1.2, 1.0) + vec3(0.0, waveTime * -0.06, 0.0), waveTime * 0.4 + 6.666, gradient);
|
||||
v_noise.x = n3;
|
||||
|
||||
float isEnd = 1.0 - step(pos.x, 0.2499);
|
||||
pos.z += isEnd * 0.01;
|
||||
v_noise.z = isEnd;
|
||||
|
||||
float offset = 0.03;
|
||||
vec3 biTangent = cross(calcNormal, tangent.xyz);
|
||||
calcNormal = cross(displace(position.xyz - tangent.xyz * offset, waveTime) - pos, displace(position.xyz - biTangent.xyz * offset, waveTime) - pos);
|
||||
|
||||
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
|
||||
v_texcoord = uv;
|
||||
v_worldPosition = (modelMatrix * vec4(pos, 1.0)).xyz;
|
||||
v_viewNormal = normalMatrix * calcNormal;
|
||||
}
|
||||
`;
|
||||
|
||||
const FRAGMENT_SHADER = `
|
||||
precision highp float;
|
||||
|
||||
uniform vec4 u_light;
|
||||
|
||||
varying vec2 v_texcoord;
|
||||
varying vec4 v_noise;
|
||||
varying vec3 v_worldPosition;
|
||||
varying vec3 v_viewNormal;
|
||||
|
||||
float map(float value, float min1, float max1, float min2, float max2) {
|
||||
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
||||
}
|
||||
|
||||
vec3 inverseTransformDirection(in vec3 dir, in mat4 matrix) {
|
||||
return normalize((vec4(dir, 0.0) * matrix).xyz);
|
||||
}
|
||||
|
||||
vec3 paletteBrand(float t) {
|
||||
vec3 deep = vec3(0.263, 0.220, 0.792);
|
||||
vec3 mid = vec3(0.310, 0.275, 0.898);
|
||||
vec3 bright = vec3(0.506, 0.549, 0.973);
|
||||
vec3 accent = vec3(0.024, 0.714, 0.831);
|
||||
t = clamp(t, 0.0, 1.0);
|
||||
if (t < 0.33) return mix(deep, mid, t / 0.33);
|
||||
if (t < 0.66) return mix(mid, bright, (t - 0.33) / 0.33);
|
||||
return mix(bright, accent, (t - 0.66) / 0.34);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float n = v_noise.x;
|
||||
float nFine = v_noise.y;
|
||||
float uv_y = v_texcoord.y;
|
||||
float uv_x = v_texcoord.x;
|
||||
|
||||
float noiseRemap = n * 0.5 + 0.5;
|
||||
float fineRemap = nFine * 0.5 + 0.5;
|
||||
float edgeFade = smoothstep(0.0, 0.15, uv_y) * smoothstep(1.0, 0.85, uv_y);
|
||||
edgeFade *= smoothstep(0.0, 0.1, uv_x) * smoothstep(1.0, 0.9, uv_x);
|
||||
|
||||
float colorT = noiseRemap * 0.6 + fineRemap * 0.15 + uv_y * 0.25;
|
||||
colorT += sin(uv_x * 3.14159 + n * 1.2) * 0.08;
|
||||
colorT = clamp(colorT, 0.0, 1.0);
|
||||
|
||||
vec3 color = paletteBrand(colorT);
|
||||
color *= mix(0.55, 1.0, edgeFade);
|
||||
|
||||
vec3 viewDir = normalize(cameraPosition - v_worldPosition);
|
||||
vec3 viewNormal = normalize(v_viewNormal);
|
||||
vec3 worldNormal = inverseTransformDirection(viewNormal, viewMatrix);
|
||||
vec3 lightDir = u_light.xyz;
|
||||
|
||||
float dirLight = max(dot(lightDir, worldNormal), 0.0);
|
||||
|
||||
vec3 halfwayVector = normalize(lightDir + viewDir);
|
||||
float shininess = 6.0;
|
||||
float specular = pow(max(dot(halfwayVector, worldNormal), 0.0), shininess);
|
||||
specular = smoothstep(0.0, 1.5, specular);
|
||||
vec3 specularColor = mix(vec3(0.5, 0.5, 1.0), vec3(1.0), 0.5) * specular;
|
||||
|
||||
float highlights = smoothstep(0.15, 0.85, fineRemap) * 0.2;
|
||||
dirLight = map(dirLight, 0.0, 1.0, 0.5, 0.78);
|
||||
|
||||
vec3 lightedColor = dirLight * color;
|
||||
lightedColor += specularColor;
|
||||
lightedColor += highlights * vec3(0.024, 0.714, 0.831);
|
||||
|
||||
float fresnel = 1.0 - pow(abs(dot(viewNormal, viewDir)), 0.25);
|
||||
lightedColor += fresnel * vec3(0.15, 0.12, 0.40);
|
||||
|
||||
lightedColor = mix(lightedColor, vec3(1.0), 0.08);
|
||||
|
||||
float edgeSoftness = 1.0 - fresnel * 0.3;
|
||||
edgeSoftness *= mix(0.85, 1.0, edgeFade);
|
||||
gl_FragColor = vec4(lightedColor, edgeSoftness);
|
||||
}
|
||||
`;
|
||||
|
||||
const STARTING_WAVE_HEIGHT = -0.6;
|
||||
|
||||
function easeOutQuart(t: number) {
|
||||
return 1 - Math.pow(1 - t, 4);
|
||||
}
|
||||
|
||||
type ColorWaveBackgroundProps = {
|
||||
yOffset?: number;
|
||||
scale?: number;
|
||||
speed?: number;
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
indigo: "#4f46e5",
|
||||
cyan: "#06b6d4",
|
||||
slate: "#0f172a",
|
||||
};
|
||||
|
||||
export default function ColorWaveBackground(props: ColorWaveBackgroundProps) {
|
||||
const canvasRef = { current: null as HTMLCanvasElement | null };
|
||||
let animationId: number | undefined;
|
||||
export function ColorWaveBackground(props: ColorWaveBackgroundProps) {
|
||||
let containerRef: HTMLDivElement | undefined;
|
||||
const yOffset = props.yOffset ?? 0.25;
|
||||
const scale = props.scale ?? 1;
|
||||
const speed = props.speed ?? 0.55;
|
||||
|
||||
onMount(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
onMount(async () => {
|
||||
if (!containerRef) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
const THREE = await import("three");
|
||||
|
||||
const reducedMotion = window.matchMedia(
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
|
||||
const resize = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = canvas.clientWidth * dpr;
|
||||
canvas.height = canvas.clientHeight * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
};
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
powerPreference: "high-performance",
|
||||
});
|
||||
renderer.setClearColor(0xffffff, 0);
|
||||
renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
|
||||
renderer.setSize(containerRef.clientWidth, containerRef.clientHeight);
|
||||
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
const canvas = renderer.domElement;
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.top = "0";
|
||||
canvas.style.left = "0";
|
||||
canvas.style.width = "100%";
|
||||
canvas.style.height = "100%";
|
||||
containerRef.appendChild(canvas);
|
||||
|
||||
const speed = props.speed ?? 1;
|
||||
const scale = props.scale ?? 1;
|
||||
const t0 = Date.now();
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
const blobs = [
|
||||
{
|
||||
x: 0.2,
|
||||
y: 0.3,
|
||||
r: 0.35,
|
||||
color: COLORS.indigo,
|
||||
phase: 0,
|
||||
},
|
||||
{
|
||||
x: 0.7,
|
||||
y: 0.6,
|
||||
r: 0.4,
|
||||
color: COLORS.cyan,
|
||||
phase: 2.1,
|
||||
},
|
||||
{
|
||||
x: 0.5,
|
||||
y: 0.8,
|
||||
r: 0.3,
|
||||
color: COLORS.slate,
|
||||
phase: 4.2,
|
||||
},
|
||||
];
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
50,
|
||||
containerRef.clientWidth / containerRef.clientHeight,
|
||||
0.1,
|
||||
100,
|
||||
);
|
||||
camera.position.z = 1;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
const draw = () => {
|
||||
const w = canvas.clientWidth;
|
||||
const h = canvas.clientHeight;
|
||||
const t = (Date.now() - t0) * 0.001 * speed;
|
||||
const isMobile = window.innerWidth <= 600;
|
||||
const segmentMultiplier = isMobile ? 0.7 : 1;
|
||||
const geometry = new THREE.PlaneGeometry(
|
||||
0.5,
|
||||
2,
|
||||
Math.floor(132 * segmentMultiplier),
|
||||
Math.floor(200 * segmentMultiplier),
|
||||
);
|
||||
geometry.computeVertexNormals();
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
blobs.forEach((blob) => {
|
||||
const ox = Math.sin(t + blob.phase) * w * 0.15;
|
||||
const oy = Math.cos(t * 0.7 + blob.phase) * h * 0.12;
|
||||
const x = (blob.x + (props.yOffset ?? 0) * 0.001) * w + ox;
|
||||
const y = blob.y * h + oy;
|
||||
const r = blob.r * Math.min(w, h) * scale;
|
||||
|
||||
const grad = ctx.createRadialGradient(x, y, 0, x, y, r);
|
||||
grad.addColorStop(0, blob.color + "40");
|
||||
grad.addColorStop(1, blob.color + "00");
|
||||
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, r, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
if (!reducedMotion) {
|
||||
animationId = requestAnimationFrame(draw);
|
||||
}
|
||||
};
|
||||
|
||||
draw();
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
if (animationId !== undefined) {
|
||||
cancelAnimationFrame(animationId);
|
||||
const tangentCount = geometry.attributes.position.count;
|
||||
const tangentArray = new Float32Array(tangentCount * 4);
|
||||
for (let i = 0; i < tangentCount; i++) {
|
||||
tangentArray[i * 4] = 1;
|
||||
tangentArray[i * 4 + 1] = 0;
|
||||
tangentArray[i * 4 + 2] = 0;
|
||||
tangentArray[i * 4 + 3] = 1;
|
||||
}
|
||||
});
|
||||
geometry.setAttribute(
|
||||
"tangent",
|
||||
new THREE.BufferAttribute(tangentArray, 4),
|
||||
);
|
||||
|
||||
const setRef = (el: HTMLCanvasElement) => {
|
||||
canvasRef.current = el;
|
||||
};
|
||||
const uniforms = {
|
||||
u_wave: { value: new THREE.Vector3(0, STARTING_WAVE_HEIGHT, 0) },
|
||||
u_light: { value: new THREE.Vector4(-1, -1, 1, 1) },
|
||||
u_scale: { value: scale },
|
||||
};
|
||||
|
||||
const material = new THREE.ShaderMaterial({
|
||||
vertexShader: VERTEX_SHADER,
|
||||
fragmentShader: FRAGMENT_SHADER,
|
||||
uniforms,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
const isMobileAtMount = window.innerWidth <= 600;
|
||||
const baseYOffset = isMobileAtMount ? yOffset - 0.08 : yOffset;
|
||||
|
||||
function setPlaneTransform() {
|
||||
mesh.rotation.set(
|
||||
Math.PI / 2,
|
||||
Math.PI / -0.5 + Math.PI * 0.02,
|
||||
Math.PI / 2 + Math.PI * 0.09,
|
||||
);
|
||||
mesh.scale.set(2, 2.5, 1);
|
||||
mesh.position.y = baseYOffset;
|
||||
if (isMobileAtMount) {
|
||||
mesh.scale.multiplyScalar(0.8);
|
||||
}
|
||||
}
|
||||
setPlaneTransform();
|
||||
scene.add(mesh);
|
||||
|
||||
let animationId = 0;
|
||||
const timer = new THREE.Timer();
|
||||
let introStartTime = 0;
|
||||
let introComplete = false;
|
||||
let canvasOpacity = 0;
|
||||
|
||||
function animate(timestamp = 0) {
|
||||
timer.update();
|
||||
const delta = timer.getDelta();
|
||||
const elapsed = timer.getElapsed();
|
||||
|
||||
uniforms.u_wave.value.x += delta * speed;
|
||||
uniforms.u_light.value.x = Math.sin(elapsed * 0.5);
|
||||
uniforms.u_light.value.y = -1 + 0.2 * Math.sin(elapsed);
|
||||
|
||||
if (!introComplete) {
|
||||
if (introStartTime === 0) introStartTime = timestamp;
|
||||
const progress = Math.min((timestamp - introStartTime) / 3000, 1);
|
||||
const eased = easeOutQuart(progress);
|
||||
uniforms.u_wave.value.y =
|
||||
STARTING_WAVE_HEIGHT + (0 - STARTING_WAVE_HEIGHT) * eased;
|
||||
|
||||
if (progress < 1) {
|
||||
canvasOpacity = Math.min(canvasOpacity + delta / 2, 1);
|
||||
canvas.style.opacity = String(canvasOpacity);
|
||||
} else {
|
||||
introComplete = true;
|
||||
canvas.style.opacity = "1";
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefersReducedMotion) {
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
uniforms.u_wave.value.y = 0;
|
||||
canvas.style.opacity = "1";
|
||||
renderer.render(scene, camera);
|
||||
} else {
|
||||
canvas.style.opacity = "0";
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
let lastWidth = containerRef.clientWidth;
|
||||
|
||||
function onResize() {
|
||||
const w = containerRef?.clientWidth ?? lastWidth;
|
||||
if (w === lastWidth) return;
|
||||
lastWidth = w;
|
||||
const h = containerRef?.clientHeight ?? 1080;
|
||||
renderer.setSize(w, h);
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
setPlaneTransform();
|
||||
}
|
||||
|
||||
function onVisibilityChange() {
|
||||
uniforms.u_wave.value.x = 0;
|
||||
}
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||
|
||||
onCleanup(() => {
|
||||
cancelAnimationFrame(animationId);
|
||||
window.removeEventListener("resize", onResize);
|
||||
document.removeEventListener("visibilitychange", onVisibilityChange);
|
||||
geometry.dispose();
|
||||
material.dispose();
|
||||
renderer.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={setRef}
|
||||
class={cn("absolute inset-0 w-full h-full", props.class)}
|
||||
style="pointer-events: none;"
|
||||
<div
|
||||
ref={containerRef}
|
||||
class="fixed top-0 left-0 pointer-events-none"
|
||||
style={{ width: "100lvw", height: "100lvh", "z-index": 0 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,21 @@ interface Feature {
|
||||
function DarkWatchIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M16 2L4 8v10c0 7.4 5.1 14.1 12 16 6.9-1.9 12-8.6 12-16V8L16 2zm0 2.8L26 9.5v8.5c0 6.1-4.2 11.7-10 13.4C10.2 29.7 6 24.1 6 18V9.5L16 4.8z"
|
||||
fill="currentColor"
|
||||
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -31,16 +35,24 @@ function DarkWatchIcon() {
|
||||
function VoicePrintIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M14 2h4v4h-4V2zM10 6h12v2h-2v10h2v2H10v-2h2V8h-2V6z"
|
||||
fill="currentColor"
|
||||
d="M12 4a4 4 0 00-4 4v8a4 4 0 008 0V8a4 4 0 00-4-4z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M4 11v2m16-2v2M8 18.5A6 6 0 0016 18.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -49,16 +61,25 @@ function VoicePrintIcon() {
|
||||
function SpamShieldIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M10 2h12l4 4v4c0 8-4 14-8 16-4-2-8-8-8-16V6l4-4zm1 2L8 8v3c0 6.7 3.4 12.2 8 14.4 4.6-2.2 8-7.7 8-14.4V8l-3-4H11z"
|
||||
fill="currentColor"
|
||||
d="M12 2l9 4v6c0 5.55-3.84 10.74-9 12-5.16-1.26-9-6.45-9-12V6l9-4z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10 10l4 4m0-4l-4 4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -67,16 +88,19 @@ function SpamShieldIcon() {
|
||||
function HomeTitleIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M16 4L4 12v14h8v-8h8v8h8V12L16 4zm0 3.2L24 12v2H8v-2L16 7.2z"
|
||||
fill="currentColor"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -85,16 +109,19 @@ function HomeTitleIcon() {
|
||||
function RemoveBrokersIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M12 4h8v2h-8V4zm-2 4h12l2 2v2H8v-2l2-2zm-2 4h16v12H8V12zm2 2v8h12v-8H10z"
|
||||
fill="currentColor"
|
||||
d="M3 6h18M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2m-4 4v6m-4-6v6m-3-8v10a2 2 0 002 2h10a2 2 0 002-2V8H8z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -103,16 +130,25 @@ function RemoveBrokersIcon() {
|
||||
function FamilyPlansIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M12 6c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4zm8 0c-2.2 0-4 1.8-4 4s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4zM8 16c-2.8 0-5 2.2-5 5v5h10v-5c0-2.8-2.2-5-5-5zm10 0c-2.8 0-5 2.2-5 5v5h10v-5c0-2.8-2.2-5-5-5z"
|
||||
fill="currentColor"
|
||||
d="M12 12a4 4 0 100-8 4 4 0 000 8zM5 22v-2a4 4 0 014-4h6a4 4 0 014 4v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M20 9a3 3 0 100-6 3 3 0 000 6zM16 22v-2a3 3 0 00-3-3h-2a3 3 0 00-3 3v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
opacity="0.6"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -15,8 +15,11 @@ function CheckIcon() {
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M7.5 12L4 8.5L5.1 7.4L7.5 9.8L12.4 4.9L13.5 6L7.5 12Z"
|
||||
fill="var(--color-success)"
|
||||
d="M4 9l3 3 7-7"
|
||||
stroke="var(--color-success)"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -15,12 +15,19 @@ function ShieldIcon() {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-12 h-12 md:w-16 md:h-16"
|
||||
>
|
||||
<circle cx="24" cy="24" r="24" class="gradient-primary" />
|
||||
<circle cx="24" cy="24" r="24" fill="var(--color-brand-primary)" />
|
||||
<path
|
||||
d="M24 8L14 12V20C14 28.4 19.2 36 24 38C28.8 36 34 28.4 34 20V12L24 8Z"
|
||||
d="M24 10L16 14v6.5c0 5.1 3.4 9.9 8 11.5 4.6-1.6 8-6.4 8-11.5V14l-8-4z"
|
||||
fill="white"
|
||||
fill-opacity="0.9"
|
||||
/>
|
||||
<path
|
||||
d="M20 24l3 2.5 5-5"
|
||||
stroke="var(--color-brand-primary)"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,16 +13,25 @@ interface Step {
|
||||
function EnrollIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-white"
|
||||
>
|
||||
<path
|
||||
d="M16 4C9.373 4 4 9.373 4 16s5.373 12 12 12 12-5.373 12-12S22.627 4 16 4zm0 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S6 21.523 6 16 10.477 6 16 6zm-1 3v5H9v2h6v5h2v-5h6v-2h-6V9h-2z"
|
||||
fill="currentColor"
|
||||
d="M12 4a4 4 0 100 8 4 4 0 000-8zM6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M17 8l2 2 4-4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -31,16 +40,31 @@ function EnrollIcon() {
|
||||
function MonitorIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-white"
|
||||
>
|
||||
<path
|
||||
d="M4 8v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2H6a2 2 0 00-2 2zm2 0h20v12H6V8zm-4 4h4v2H4v-2zm16 0h4v2h-4v-2z"
|
||||
fill="currentColor"
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M12 8v4l3 3"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 12h3m15 0h3M12 3v3m0 15v3"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
opacity="0.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -49,17 +73,30 @@ function MonitorIcon() {
|
||||
function AlertIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-white"
|
||||
>
|
||||
<path
|
||||
d="M16 2L4 8v10c0 7.4 5.1 14.1 12 16 6.9-1.9 12-8.6 12-16V8L16 2zm0 2.8L26 9.5v8.5c0 6.1-4.2 11.7-10 13.4C10.2 29.7 6 24.1 6 18V9.5L16 4.8zM15 10v2h2v-2h-2zm0 4v6h2v-6h-2z"
|
||||
d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12 22a2 2 0 01-2-2h4a2 2 0 01-2 2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M12 11v3m0-6v1"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -88,41 +125,43 @@ const steps: Step[] = [
|
||||
},
|
||||
];
|
||||
|
||||
interface StepItemProps {
|
||||
interface StepBlockProps {
|
||||
step: Step;
|
||||
index: number;
|
||||
}
|
||||
|
||||
function StepItem(props: StepItemProps) {
|
||||
function StepBlock(props: StepBlockProps) {
|
||||
const isEven = props.index % 2 === 0;
|
||||
const Icon = props.step.icon;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={cn(
|
||||
"flex items-center gap-8 md:gap-16",
|
||||
isEven ? "md:flex-row" : "md:flex-row-reverse",
|
||||
"flex gap-8 md:flex-row flex-col",
|
||||
isEven ? "" : "md:flex-row-reverse",
|
||||
)}
|
||||
>
|
||||
<div class={cn("flex-shrink-0", isEven ? "" : "md:order-last")}>
|
||||
<div class="w-16 h-16 rounded-full gradient-primary shadow-glow-primary flex items-center justify-center">
|
||||
<Icon />
|
||||
<div class="flex-1">
|
||||
<div class="flex items-start gap-5">
|
||||
<div class="w-14 h-14 rounded-full gradient-primary shadow-glow-primary flex items-center justify-center shrink-0">
|
||||
<Icon />
|
||||
</div>
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 mb-1.5">
|
||||
<span class="text-sm font-semibold text-[var(--color-brand-primary)]">
|
||||
Step {props.step.number}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="text-xl md:text-2xl font-bold text-[var(--color-text-primary)] mb-2">
|
||||
{props.step.title}
|
||||
</h3>
|
||||
<p class="text-base text-[var(--color-text-secondary)] leading-relaxed">
|
||||
{props.step.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={cn("flex-1", isEven ? "" : "md:text-right")}>
|
||||
<div class="inline-flex items-center gap-2 mb-2">
|
||||
<span class="text-sm font-semibold text-[var(--color-brand-primary)]">
|
||||
Step {props.step.number}
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="text-2xl md:text-3xl font-bold text-[var(--color-text-primary)] mb-3">
|
||||
{props.step.title}
|
||||
</h3>
|
||||
<p class="text-lg text-[var(--color-text-secondary)] leading-relaxed max-w-lg">
|
||||
{props.step.description}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-1 hidden md:block" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -135,7 +174,7 @@ export default function HowItWorksSection(props: HowItWorksSectionProps) {
|
||||
return (
|
||||
<section
|
||||
id="how-it-works"
|
||||
class={cn("bg-dot-grid py-20 md:py-28 scroll-mt-16", props.class)}
|
||||
class={cn("py-20 md:py-28 scroll-mt-16", props.class)}
|
||||
>
|
||||
<PageContainer py="py-8">
|
||||
<div class="text-center mb-16">
|
||||
@@ -149,7 +188,7 @@ export default function HowItWorksSection(props: HowItWorksSectionProps) {
|
||||
|
||||
<div class="flex flex-col gap-12 md:gap-16">
|
||||
<For each={steps}>
|
||||
{(step, index) => <StepItem step={step} index={index()} />}
|
||||
{(step, index) => <StepBlock step={step} index={index()} />}
|
||||
</For>
|
||||
</div>
|
||||
</PageContainer>
|
||||
|
||||
@@ -15,8 +15,11 @@ function CheckIcon() {
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
<path
|
||||
d="M7.5 12L4 8.5L5.1 7.4L7.5 9.8L12.4 4.9L13.5 6L7.5 12Z"
|
||||
fill="var(--color-success)"
|
||||
d="M4 9l3 3 7-7"
|
||||
stroke="var(--color-success)"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -25,16 +28,26 @@ function CheckIcon() {
|
||||
function ProactiveIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M16 2L4 8v10c0 7.4 5.1 14.1 12 16 6.9-1.9 12-8.6 12-16V8L16 2zm0 2.8L26 9.5v8.5c0 6.1-4.2 11.7-10 13.4C10.2 29.7 6 24.1 6 18V9.5L16 4.8z"
|
||||
fill="currentColor"
|
||||
d="M13 3l-2 6h5l-3 8"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4 14l5-5m0 0l5 5m-5-5v12"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -43,16 +56,25 @@ function ProactiveIcon() {
|
||||
function AIIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M14 2h4v4h-4V2zM6 8h4v4H6V8zm16 0h4v4h-4V8zM6 16h4v4H6v-4zm16 0h4v4h-4v-4zm-8 8h4v4h-4v-4zM10 14h4v4h-4v-4zm8 0h4v4h-4v-4zM10 22h4v4h-4v-4zm8 0h4v4h-4v-4z"
|
||||
fill="currentColor"
|
||||
d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 16l2 2-2 2M16 16l-2 2 2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -61,16 +83,26 @@ function AIIcon() {
|
||||
function PrivacyIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 text-[var(--color-brand-primary)]"
|
||||
class="w-6 h-6 text-[var(--color-brand-primary)]"
|
||||
>
|
||||
<path
|
||||
d="M14 4h4v4h-4V4zM10 8h12l2 2v2H8v-2l2-2zm-2 4h16v14H8V12zm2 2v10h12v-10H10z"
|
||||
fill="currentColor"
|
||||
d="M12 2l9 4v6c0 5.55-3.84 10.74-9 12-5.16-1.26-9-6.45-9-12V6l9-4z"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M9 12l2 2 4-4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -109,7 +109,7 @@ const socialLinks = [
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer class="border-t border-[var(--color-border)] bg-[var(--color-bg-secondary)]">
|
||||
<footer class="border-t border-(--color-border) bg-dot-grid z-10 relative">
|
||||
<div class="max-w-7xl mx-auto px-4 md:px-6 lg:px-8 py-12">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-8 lg:gap-12">
|
||||
<div class="lg:col-span-2">
|
||||
@@ -169,7 +169,7 @@ export default function Footer() {
|
||||
|
||||
<div class="mt-12 pt-8 border-t border-[var(--color-border)] flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p class="text-sm text-[var(--color-text-tertiary)]">
|
||||
{'\u00A9'} {new Date().getFullYear()} ShieldAI. All rights reserved.
|
||||
{"\u00A9"} {new Date().getFullYear()} ShieldAI. All rights reserved.
|
||||
</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<A
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
CTABannerSection,
|
||||
} from "~/components/landing";
|
||||
|
||||
const cut = "clamp(16px, 2.5vw, 40px)";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main class="relative overflow-hidden">
|
||||
<main
|
||||
class="relative overflow-hidden"
|
||||
style="--cut: clamp(16px, 2.5vw, 40px)"
|
||||
>
|
||||
<Title>ShieldAI — AI-Powered Identity Protection</Title>
|
||||
|
||||
<div class="relative">
|
||||
@@ -24,38 +25,40 @@ export default function Home() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-dot-grid"
|
||||
style={{
|
||||
"clip-path": `polygon(0 var(--cut, ${cut}), 100% 0, 100% 100%, 0 100%)`,
|
||||
"clip-path": "polygon(0 var(--cut), 100% 0, 100% 100%, 0 100%)",
|
||||
}}
|
||||
>
|
||||
<HowItWorksSection />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative z-10 backdrop-blur-2xl bg-bg/40 py-8 -my-10"
|
||||
style={{
|
||||
"clip-path": `polygon(0 0, 100% 0, 100% calc(100% - var(--cut, ${cut})), 0 100%)`,
|
||||
"clip-path":
|
||||
"polygon(0 0, 100% 0, 100% calc(100% - var(--cut)), 0 100%)",
|
||||
}}
|
||||
>
|
||||
<FeaturesGridSection />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bg-dot-grid"
|
||||
style={{
|
||||
"clip-path": `polygon(0 var(--cut, ${cut}), 100% 0, 100% 100%, 0 100%)`,
|
||||
"clip-path": "polygon(0 var(--cut), 100% 0, 100% 100%, 0 100%)",
|
||||
}}
|
||||
>
|
||||
<ForUsersSection />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative z-10 backdrop-blur-2xl bg-bg/40 pt-8 -mt-10"
|
||||
style={{
|
||||
"clip-path": `polygon(0 0, 100% 0, 100% calc(100% - var(--cut, ${cut})), 0 100%)`,
|
||||
"clip-path": "polygon(0 var(--cut), 100% 0, 100% 100%, 0 100%)",
|
||||
}}
|
||||
>
|
||||
<WhyShieldAISection />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<CTABannerSection />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user