diff --git a/web/src/components/landing/CTABannerSection.tsx b/web/src/components/landing/CTABannerSection.tsx index c5e79a2..a2cafbd 100644 --- a/web/src/components/landing/CTABannerSection.tsx +++ b/web/src/components/landing/CTABannerSection.tsx @@ -9,12 +9,9 @@ interface CTABannerSectionProps { export default function CTABannerSection(props: CTABannerSectionProps) { return ( -
+
-
+

Ready to protect your identity?

diff --git a/web/src/components/landing/ColorWaveBackground.tsx b/web/src/components/landing/ColorWaveBackground.tsx index a521742..0ba83a8 100644 --- a/web/src/components/landing/ColorWaveBackground.tsx +++ b/web/src/components/landing/ColorWaveBackground.tsx @@ -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 ( - ); } diff --git a/web/src/components/landing/FeaturesGridSection.tsx b/web/src/components/landing/FeaturesGridSection.tsx index da47bca..678d8a7 100644 --- a/web/src/components/landing/FeaturesGridSection.tsx +++ b/web/src/components/landing/FeaturesGridSection.tsx @@ -13,17 +13,21 @@ interface Feature { function DarkWatchIcon() { return ( + ); } @@ -31,16 +35,24 @@ function DarkWatchIcon() { function VoicePrintIcon() { return ( + ); @@ -49,16 +61,25 @@ function VoicePrintIcon() { function SpamShieldIcon() { return ( + ); @@ -67,16 +88,19 @@ function SpamShieldIcon() { function HomeTitleIcon() { return ( ); @@ -85,16 +109,19 @@ function HomeTitleIcon() { function RemoveBrokersIcon() { return ( ); @@ -103,16 +130,25 @@ function RemoveBrokersIcon() { function FamilyPlansIcon() { return ( + ); diff --git a/web/src/components/landing/ForUsersSection.tsx b/web/src/components/landing/ForUsersSection.tsx index 426eae7..a72adae 100644 --- a/web/src/components/landing/ForUsersSection.tsx +++ b/web/src/components/landing/ForUsersSection.tsx @@ -15,8 +15,11 @@ function CheckIcon() { class="flex-shrink-0" > ); diff --git a/web/src/components/landing/HeroSection.tsx b/web/src/components/landing/HeroSection.tsx index 5c3224b..987ce42 100644 --- a/web/src/components/landing/HeroSection.tsx +++ b/web/src/components/landing/HeroSection.tsx @@ -15,12 +15,19 @@ function ShieldIcon() { xmlns="http://www.w3.org/2000/svg" class="w-12 h-12 md:w-16 md:h-16" > - + + ); } diff --git a/web/src/components/landing/HowItWorksSection.tsx b/web/src/components/landing/HowItWorksSection.tsx index f1f0d8c..3c80ec2 100644 --- a/web/src/components/landing/HowItWorksSection.tsx +++ b/web/src/components/landing/HowItWorksSection.tsx @@ -13,16 +13,25 @@ interface Step { function EnrollIcon() { return ( + ); @@ -31,16 +40,31 @@ function EnrollIcon() { function MonitorIcon() { return ( + + ); @@ -49,17 +73,30 @@ function MonitorIcon() { function AlertIcon() { return ( + + ); } @@ -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 (
-
-
- +
+
+
+ +
+
+
+ + Step {props.step.number} + +
+

+ {props.step.title} +

+

+ {props.step.description} +

+
- -
-
- - Step {props.step.number} - -
-

- {props.step.title} -

-

- {props.step.description} -

-
+ ); } @@ -135,7 +174,7 @@ export default function HowItWorksSection(props: HowItWorksSectionProps) { return (
@@ -149,7 +188,7 @@ export default function HowItWorksSection(props: HowItWorksSectionProps) {
- {(step, index) => } + {(step, index) => }
diff --git a/web/src/components/landing/WhyShieldAISection.tsx b/web/src/components/landing/WhyShieldAISection.tsx index dcd9edd..53611f3 100644 --- a/web/src/components/landing/WhyShieldAISection.tsx +++ b/web/src/components/landing/WhyShieldAISection.tsx @@ -15,8 +15,11 @@ function CheckIcon() { class="flex-shrink-0" > ); @@ -25,16 +28,26 @@ function CheckIcon() { function ProactiveIcon() { return ( + ); @@ -43,16 +56,25 @@ function ProactiveIcon() { function AIIcon() { return ( + ); @@ -61,16 +83,26 @@ function AIIcon() { function PrivacyIcon() { return ( + ); diff --git a/web/src/components/layout/Footer.tsx b/web/src/components/layout/Footer.tsx index b7e01b1..b52f80f 100644 --- a/web/src/components/layout/Footer.tsx +++ b/web/src/components/layout/Footer.tsx @@ -109,7 +109,7 @@ const socialLinks = [ export default function Footer() { return ( -