This commit is contained in:
2026-05-25 16:31:39 -04:00
parent bec8cbf269
commit e6b07ddf1d
9 changed files with 695 additions and 209 deletions

View File

@@ -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>

View File

@@ -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 }}
/>
);
}

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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

View File

@@ -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>