feat: replace landing page with shader hero

This commit is contained in:
2026-04-19 16:10:00 +01:00
parent 2f74ff0c30
commit c44043f38d
2 changed files with 306 additions and 421 deletions

View File

@@ -110,8 +110,8 @@
--color-sidebar-ring: var(--sidebar-ring);
/* Custom brand colors and glassmorphism tokens */
--color-premium: #2563eb;
--color-premium-foreground: #ffffff;
--color-premium: #ff7a59;
--color-premium-foreground: #17110d;
--color-glass-background: rgb(25 25 30 / 60%);
--color-glass-border: rgb(255 255 255 / 8%);
--color-glass-panel: rgb(15 15 20 / 70%);

View File

@@ -1,457 +1,342 @@
<script lang="ts">
// @ts-nocheck
import { Button } from '$lib/components/ui/button/index.js';
import {
ArrowRight,
BellRing,
Camera,
MonitorSmartphone,
Radar,
Shield,
Sparkles,
ChevronRight,
Activity,
Lock,
Zap,
Globe,
GitFork
} from '@lucide/svelte';
import { fade, fly, scale } from 'svelte/transition';
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { Button } from '$lib/components/ui/button/index.js';
import { ArrowRight } from '@lucide/svelte';
let mounted = $state(false);
let shaderCanvas: HTMLCanvasElement | null = null;
const heroPoints = ['Camera station', 'Client viewer', 'Motion events'];
const vertexShaderSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
const fragmentShaderSource = `
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 5; i++) {
value += amplitude * noise(p);
p = p * 2.0 + vec2(17.1, 9.2);
amplitude *= 0.5;
}
return value;
}
mat2 rot(float angle) {
float s = sin(angle);
float c = cos(angle);
return mat2(c, -s, s, c);
}
void main() {
vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;
float t = u_time * 0.18;
vec2 q = p * 1.45;
q *= rot(-0.25);
float fieldA = fbm(q * 2.1 + vec2(t, -t * 0.35));
float fieldB = fbm(q * 3.0 - vec2(t * 0.7, -t));
float ribbonA = smoothstep(
0.62,
0.96,
1.0 - abs(p.y + 0.20 * sin(p.x * 2.7 + t * 1.8) + (fieldA - 0.5) * 0.30)
);
float ribbonB = smoothstep(
0.58,
0.95,
1.0 - abs(p.y - 0.26 * cos(p.x * 1.9 - t * 1.2) + (fieldB - 0.5) * 0.22)
);
float glow = exp(-3.4 * length(p * vec2(0.8, 1.15)));
float grain = noise(gl_FragCoord.xy * 0.35 + t * 8.0) * 0.04;
vec3 base = vec3(0.055, 0.05, 0.07);
vec3 underpaint = vec3(0.14, 0.09, 0.08);
vec3 accentA = vec3(1.0, 0.48, 0.35);
vec3 accentB = vec3(0.99, 0.67, 0.46);
vec3 color = base;
color += underpaint * (fieldA * 0.38 + fieldB * 0.18);
color += accentA * ribbonA * 0.52;
color += accentB * ribbonB * 0.34;
color += accentA * glow * 0.12;
color += grain;
float vignette = smoothstep(1.35, 0.18, length(p));
color *= vignette;
color += vec3(0.012, 0.012, 0.018);
gl_FragColor = vec4(color, 1.0);
}
`;
const createShader = (gl: WebGLRenderingContext, type: number, source: string) => {
const shader = gl.createShader(type);
if (!shader) {
return null;
}
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 createProgram = (gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string) => {
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
if (!vertexShader || !fragmentShader) {
return null;
}
const program = gl.createProgram();
if (!program) {
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return null;
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
return null;
}
return { program, vertexShader, fragmentShader };
};
onMount(() => {
mounted = true;
if (!shaderCanvas) {
return;
}
const gl = shaderCanvas.getContext('webgl', {
alpha: true,
antialias: true,
premultipliedAlpha: false
});
const operationalRoles = [
{
title: 'Camera Station',
description:
'Use the browser as a live capture point with motion detection controls, camera input selection, and feed health monitoring.',
icon: Camera,
color: 'text-sky-400'
},
{
title: 'Client Viewer',
description:
'Watch linked cameras, review recordings, inspect diagnostics, and respond to motion events from a single console.',
icon: MonitorSmartphone,
color: 'text-indigo-400'
if (!gl) {
return;
}
];
const features = [
{
title: 'Zero Latency',
description: 'Powered by WebRTC for near-instant video streaming across devices.',
icon: Zap
},
{
title: 'Motion Detection',
description: 'On-device computer vision detects movement without cloud processing.',
icon: Activity
},
{
title: 'Secure by Design',
description: 'End-to-end encrypted signalling ensures your feeds stay private.',
icon: Lock
},
{
title: 'Global Access',
description: 'Access your security network from any browser, anywhere in the world.',
icon: Globe
const programBundle = createProgram(gl, vertexShaderSource, fragmentShaderSource);
if (!programBundle) {
return;
}
];
const workflow = [
{
kicker: '01',
title: 'Authenticate',
copy: 'Create an operator account or sign in to restore your saved browser session.'
},
{
kicker: '02',
title: 'Assign this browser',
copy: 'Register it as a camera or a client so routing, controls, and dashboards match the role.'
},
{
kicker: '03',
title: 'Run the workspace',
copy: 'Monitor feeds, motion alerts, activity history, and device health without leaving the app shell.'
const { program, vertexShader, fragmentShader } = programBundle;
const positionLocation = gl.getAttribLocation(program, 'a_position');
const timeLocation = gl.getUniformLocation(program, 'u_time');
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
gl.STATIC_DRAW
);
let frameId = 0;
let lastWidth = 0;
let lastHeight = 0;
const resize = () => {
if (!shaderCanvas) {
return;
}
];
const evidence = [
'Live browser camera preview and role-based dashboards',
'Motion event notifications, activity history, and recording playback',
'Persistent device restore so returning to the app is fast'
];
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const width = Math.floor(shaderCanvas.clientWidth * dpr);
const height = Math.floor(shaderCanvas.clientHeight * dpr);
if (width === lastWidth && height === lastHeight) {
return;
}
lastWidth = width;
lastHeight = height;
shaderCanvas.width = width;
shaderCanvas.height = height;
gl.viewport(0, 0, width, height);
};
const render = (time: number) => {
resize();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform1f(timeLocation, time * 0.001);
gl.uniform2f(resolutionLocation, shaderCanvas?.width || 0, shaderCanvas?.height || 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
frameId = window.requestAnimationFrame(render);
};
window.addEventListener('resize', resize);
resize();
frameId = window.requestAnimationFrame(render);
return () => {
window.cancelAnimationFrame(frameId);
window.removeEventListener('resize', resize);
gl.deleteBuffer(positionBuffer);
gl.deleteProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
};
});
</script>
<svelte:head>
<title>PhoneCam | Browser-Based Security Intelligence</title>
<meta
name="description"
content="PhoneCam turns any browser into a professional security console with live motion detection and WebRTC streaming."
content="PhoneCam turns any browser into a practical security console with live monitoring, motion detection, and recording playback."
/>
</svelte:head>
<div class="relative min-h-screen overflow-x-hidden bg-[#020617] font-sans text-slate-200 selection:bg-sky-500/30">
<!-- Grainy texture overlay -->
<div class="pointer-events-none fixed inset-0 z-50 opacity-[0.03]" style="background-image: url('data:image/svg+xml,%3Csvg viewBox=\'0 0 200 200\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noiseFilter\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.65\' numOctaves=\'3\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noiseFilter)\'/%3E%3C/svg%3E')"></div>
<section
class="relative min-h-screen overflow-hidden bg-[#0a0910] text-[#f6efe7]"
style="font-family: var(--landing-sans);"
>
<canvas bind:this={shaderCanvas} class="absolute inset-0 h-full w-full"></canvas>
<div class="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(10,9,16,0.24),transparent_28%),linear-gradient(180deg,rgba(10,9,16,0.45),rgba(10,9,16,0.78))]"></div>
<div class="absolute inset-0 bg-[linear-gradient(90deg,rgba(10,9,16,0.88)_0%,rgba(10,9,16,0.62)_42%,rgba(10,9,16,0.28)_100%)]"></div>
<!-- Background Gradients -->
<div class="fixed inset-0 z-0 overflow-hidden pointer-events-none">
<div class="absolute -top-[10%] -left-[10%] h-[40%] w-[40%] rounded-full bg-sky-500/10 blur-[120px]"></div>
<div class="absolute top-[20%] -right-[5%] h-[35%] w-[35%] rounded-full bg-indigo-500/10 blur-[120px]"></div>
<div class="absolute bottom-[10%] left-[20%] h-[30%] w-[30%] rounded-full bg-blue-600/5 blur-[100px]"></div>
</div>
<header class="relative z-20 px-6 py-6 sm:px-10 lg:px-14">
<div class="mx-auto flex max-w-7xl items-center justify-between gap-4">
{#if mounted}
<div in:fly={{ y: -20, duration: 800 }} class="hidden sm:block">
<p class="text-[10px] font-bold uppercase tracking-[0.3em] text-sky-400/90">Intelligence</p>
<p class="text-lg font-bold tracking-tight text-white">PhoneCam</p>
</div>
<nav in:fly={{ y: -20, duration: 800, delay: 100 }} class="flex items-center gap-2 sm:gap-6">
<div class="relative z-10 flex min-h-screen flex-col">
<header class="px-6 py-5 sm:px-8 lg:px-10">
<div class="mx-auto flex max-w-6xl items-center justify-between">
<a
href="/"
class="text-xl font-semibold tracking-tight text-[#f6efe7]"
style="font-family: var(--landing-display);"
>
PhoneCam
</a>
<nav class="flex items-center gap-2">
<Button
href="/auth/login"
variant="ghost"
class="rounded-full px-5 text-sm font-medium text-slate-300 hover:bg-white/5 hover:text-white"
class="rounded-full px-4 text-sm text-[#d7c9bd] hover:bg-white/5 hover:text-[#f6efe7]"
>
Sign in
</Button>
<Button href="/auth/signup" variant="premium" class="group relative overflow-hidden rounded-full px-6 shadow-[0_0_20px_rgba(37,99,235,0.3)] transition-all hover:shadow-[0_0_30px_rgba(37,99,235,0.5)] active:scale-95">
<span class="relative z-10 flex items-center gap-2 font-semibold">
<Button
href="/auth/signup"
variant="premium"
class="rounded-full px-5 text-sm font-semibold"
>
Get started
<ChevronRight class="size-4 transition-transform group-hover:translate-x-0.5" />
</span>
<div class="absolute inset-0 bg-gradient-to-r from-blue-600 to-indigo-600 opacity-0 transition-opacity group-hover:opacity-100"></div>
</Button>
</nav>
{/if}
</div>
</header>
<main class="relative z-10">
<!-- Hero Section -->
<section class="px-6 pb-24 pt-12 sm:px-10 lg:px-14">
<div class="mx-auto grid max-w-7xl gap-16 lg:grid-cols-[1fr_1.1fr] lg:items-center">
{#if mounted}
<div in:fly={{ x: -30, duration: 1000, delay: 200 }} class="space-y-10">
<div class="inline-flex items-center gap-2 rounded-full border border-sky-500/20 bg-sky-500/5 px-4 py-1.5 text-[11px] font-bold uppercase tracking-[0.2em] text-sky-400">
<Sparkles class="size-3.5" />
Live browser-based security
</div>
<div class="space-y-6">
<h1 class="text-5xl font-bold leading-[1.1] tracking-tight text-white sm:text-6xl xl:text-7xl">
Transform any <span class="bg-gradient-to-r from-sky-400 to-indigo-400 bg-clip-text text-transparent">browser</span> into a security hub.
<div class="mx-auto flex w-full max-w-6xl flex-1 items-center px-6 pb-12 pt-8 sm:px-8 lg:px-10">
<div in:fly={{ y: 28, duration: 500 }} class="max-w-3xl space-y-8">
<p class="text-xs font-semibold uppercase tracking-[0.32em] text-[#ff7a59]">
Live browser monitoring
</p>
<div class="space-y-5">
<p
class="text-[clamp(3.2rem,10vw,7rem)] leading-none tracking-[-0.07em] text-[#f6efe7]"
style="font-family: var(--landing-display);"
>
PhoneCam
</p>
<h1
class="max-w-2xl text-4xl font-semibold leading-tight tracking-[-0.05em] text-[#f6efe7] sm:text-5xl lg:text-6xl"
style="font-family: var(--landing-display);"
>
Monitoring that feels immediate.
</h1>
<p class="max-w-xl text-lg leading-relaxed text-slate-400 sm:text-xl">
PhoneCam turns your devices into professional security stations. No apps to install—just pure WebRTC power for live capture, motion tracking, and remote viewing.
<p class="max-w-2xl text-base leading-7 text-[#d7c9bd] sm:text-lg">
Use one browser as the camera station, another as the viewer, and keep feeds,
recordings, and activity in one place.
</p>
</div>
<div class="flex flex-col gap-4 sm:flex-row">
<Button href="/auth/signup" variant="premium" class="h-14 rounded-full px-8 text-base font-bold shadow-xl">
Start monitoring now
<ArrowRight class="ml-2 size-5" />
<div class="flex flex-col gap-3 sm:flex-row">
<Button href="/auth/signup" variant="premium" class="h-12 rounded-full px-6 text-sm font-semibold">
Create account
<ArrowRight class="ml-2 size-4" />
</Button>
<Button
href="/app"
variant="ghost"
class="h-14 rounded-full border border-white/10 bg-white/[0.02] px-8 text-base font-semibold text-white backdrop-blur-md hover:bg-white/[0.08]"
class="h-12 rounded-full border border-white/12 bg-white/[0.03] px-6 text-sm font-medium text-[#f6efe7] hover:bg-white/[0.08]"
>
Open dashboard
</Button>
</div>
<div class="flex flex-wrap items-center gap-x-8 gap-y-4 pt-4">
{#each evidence as item}
<div class="flex items-center gap-2 text-xs font-medium text-slate-500">
<div class="size-1.5 rounded-full bg-sky-500 shadow-[0_0_8px_rgba(56,189,248,0.5)]"></div>
{item}
</div>
{/each}
</div>
</div>
<div in:fly={{ x: 30, duration: 1000, delay: 400 }} class="relative">
<!-- Decorative elements -->
<div class="absolute -top-20 -right-20 size-64 rounded-full bg-sky-500/10 blur-[100px]"></div>
<div class="absolute -bottom-20 -left-20 size-64 rounded-full bg-indigo-600/10 blur-[100px]"></div>
<div class="relative group">
<!-- Frame reflection -->
<div class="absolute -inset-0.5 rounded-[2.5rem] bg-gradient-to-br from-white/20 via-transparent to-transparent opacity-50"></div>
<div class="relative overflow-hidden rounded-[2.5rem] border border-white/10 bg-slate-900/40 p-3 shadow-2xl backdrop-blur-2xl transition-all duration-700 group-hover:border-sky-500/30 group-hover:shadow-sky-500/10 sm:p-4">
<div class="rounded-[2rem] border border-white/5 bg-[#010a12] p-4 sm:p-6">
<!-- Mock UI Header -->
<div class="mb-8 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="size-3 rounded-full bg-red-500/80 shadow-[0_0_10px_rgba(239,68,68,0.4)]"></div>
<div class="size-3 rounded-full bg-yellow-500/80"></div>
<div class="size-3 rounded-full bg-green-500/80"></div>
</div>
<div class="flex items-center gap-2 rounded-full border border-emerald-500/20 bg-emerald-500/5 px-3 py-1">
<div class="size-1.5 animate-pulse rounded-full bg-emerald-400"></div>
<span class="text-[10px] font-bold uppercase tracking-wider text-emerald-400">Live Station Beta</span>
</div>
</div>
<div class="grid gap-6">
<div class="relative aspect-video overflow-hidden rounded-2xl border border-white/5 bg-slate-950">
<div class="absolute inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.4)_100%)]"></div>
<!-- Grid lines for "camera" feel -->
<div class="absolute inset-0 opacity-10" style="background-image: linear-gradient(#94a3b8 1px, transparent 1px), linear-gradient(90deg, #94a3b8 1px, transparent 1px); background-size: 40px 40px;"></div>
<div class="absolute left-6 top-6">
<p class="text-[10px] font-bold uppercase tracking-widest text-white/50">Rec • 00:12:45</p>
</div>
<div class="absolute right-6 top-6">
<div class="flex items-center gap-2 rounded-lg bg-black/40 px-2 py-1 backdrop-blur-md">
<Activity class="size-3 text-sky-400" />
<span class="text-[10px] font-bold text-white">720p @ 30fps</span>
</div>
</div>
<div class="absolute inset-0 flex items-center justify-center">
<div class="relative size-16 rounded-full border border-white/10 bg-white/5 flex items-center justify-center backdrop-blur-sm group-hover:scale-110 transition-transform">
<Radar class="size-8 text-sky-400/80 animate-pulse" />
</div>
</div>
<!-- Motion tracking box -->
<div class="absolute left-1/4 top-1/3 size-32 rounded-lg border-2 border-sky-400/30 bg-sky-400/5">
<div class="absolute -left-1 -top-1 size-2 bg-sky-400"></div>
<div class="absolute -right-1 -top-1 size-2 bg-sky-400"></div>
<div class="absolute -bottom-1 -left-1 size-2 bg-sky-400"></div>
<div class="absolute -bottom-1 -right-1 size-2 bg-sky-400"></div>
<p class="absolute -top-6 left-0 text-[10px] font-bold text-sky-400">MOTION DETECTED</p>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="rounded-xl border border-white/5 bg-white/5 p-4 transition-colors hover:bg-white/[0.08]">
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-500">Signal Strength</p>
<div class="mt-2 flex items-end gap-1">
<div class="h-2 w-1.5 rounded-sm bg-sky-400"></div>
<div class="h-3.5 w-1.5 rounded-sm bg-sky-400"></div>
<div class="h-5 w-1.5 rounded-sm bg-sky-400"></div>
<div class="h-4 w-1.5 rounded-sm bg-sky-400"></div>
<span class="ml-2 text-xl font-bold text-white">94%</span>
</div>
</div>
<div class="rounded-xl border border-white/5 bg-white/5 p-4 transition-colors hover:bg-white/[0.08]">
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-500">Security Nodes</p>
<div class="mt-2 flex items-center gap-3">
<div class="flex -space-x-2">
<div class="size-6 rounded-full border border-slate-900 bg-sky-500"></div>
<div class="size-6 rounded-full border border-slate-900 bg-indigo-500"></div>
<div class="size-6 rounded-full border border-slate-900 bg-blue-400"></div>
</div>
<span class="text-xl font-bold text-white">03</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-wrap items-center gap-3 pt-2 text-sm text-[#b7a696]">
{#each heroPoints as item, index}
<span>{item}</span>
{#if index < heroPoints.length - 1}
<span class="text-[#ff7a59]"></span>
{/if}
</div>
</section>
<!-- Features Section -->
<section class="relative border-y border-white/5 bg-slate-950/50 px-6 py-32 sm:px-10 lg:px-14">
<div class="mx-auto max-w-7xl">
<div class="mb-20 text-center">
<h2 class="text-3xl font-bold tracking-tight text-white sm:text-4xl">Intelligence at the edge.</h2>
<p class="mt-4 text-lg text-slate-400">Powerful features that work entirely within your browser.</p>
</div>
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
{#each features as feature, i}
<div
class="group rounded-3xl border border-white/5 bg-white/[0.02] p-8 transition-all hover:bg-white/[0.05] hover:shadow-2xl"
>
<div class="mb-6 flex size-12 items-center justify-center rounded-2xl bg-sky-500/10 text-sky-400 transition-colors group-hover:bg-sky-500 group-hover:text-white">
<feature.icon class="size-6" />
</div>
<h3 class="text-xl font-bold text-white">{feature.title}</h3>
<p class="mt-3 text-sm leading-relaxed text-slate-400">{feature.description}</p>
</div>
{/each}
</div>
</div>
</section>
<!-- Role Specifics -->
<section class="px-6 py-32 sm:px-10 lg:px-14">
<div class="mx-auto max-w-7xl">
<div class="grid gap-16 lg:grid-cols-2 lg:items-center">
<div>
<p class="text-[11px] font-bold uppercase tracking-[0.3em] text-indigo-400">Hybrid Architecture</p>
<h2 class="mt-6 text-4xl font-bold tracking-tight text-white sm:text-5xl">One platform, two distinct experiences.</h2>
<p class="mt-6 text-lg leading-relaxed text-slate-400">
Depending on how you sign in, PhoneCam adapts its entire interface to provide either a high-performance capture station or a comprehensive monitoring console.
</p>
<div class="mt-12 space-y-8">
{#each operationalRoles as role}
<div class="flex gap-6">
<div class="flex size-12 shrink-0 items-center justify-center rounded-2xl border border-white/10 bg-white/5 {role.color}">
<role.icon class="size-6" />
</div>
<div>
<h3 class="text-xl font-bold text-white">{role.title}</h3>
<p class="mt-2 text-slate-400">{role.description}</p>
</div>
</div>
{/each}
</div>
</div>
<div class="relative rounded-[2rem] border border-white/10 bg-gradient-to-br from-slate-900 to-black p-1">
<div class="overflow-hidden rounded-[1.9rem] bg-slate-950">
<div class="flex items-center gap-2 border-b border-white/5 bg-white/5 px-6 py-3">
<div class="size-2 rounded-full bg-white/20"></div>
<div class="size-2 rounded-full bg-white/20"></div>
<div class="size-2 rounded-full bg-white/20"></div>
<div class="ml-4 h-4 w-32 rounded-full bg-white/5"></div>
</div>
<div class="p-8">
<div class="space-y-6">
<div class="h-8 w-3/4 rounded-lg bg-white/10"></div>
<div class="space-y-3">
<div class="h-4 w-full rounded-md bg-white/5"></div>
<div class="h-4 w-full rounded-md bg-white/5"></div>
<div class="h-4 w-2/3 rounded-md bg-white/5"></div>
</div>
<div class="grid grid-cols-2 gap-4 pt-4">
<div class="aspect-square rounded-2xl bg-sky-500/20"></div>
<div class="aspect-square rounded-2xl bg-indigo-500/20"></div>
</div>
</div>
</div>
</div>
<!-- Absolute positioned floating card -->
<div class="absolute -bottom-8 -left-8 max-w-[240px] rounded-2xl border border-white/10 bg-slate-900/80 p-6 shadow-2xl backdrop-blur-xl">
<div class="flex items-center gap-4">
<div class="flex size-10 items-center justify-center rounded-xl bg-sky-500 text-white shadow-lg shadow-sky-500/20">
<Shield class="size-5" />
</div>
<div>
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-500">Security</p>
<p class="text-sm font-bold text-white">Active Guard</p>
</div>
</div>
<p class="mt-4 text-xs text-slate-400 leading-relaxed">System state is encrypted and synchronized across all active nodes.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Bottom CTA -->
<section class="px-6 py-24 sm:px-10 lg:px-14">
<div class="mx-auto max-w-7xl">
<div class="relative overflow-hidden rounded-[3rem] bg-gradient-to-br from-blue-600 to-indigo-700 px-8 py-20 text-center shadow-2xl sm:px-16 lg:py-28">
<!-- Abstract background shapes -->
<div class="absolute -left-10 -top-10 size-64 rounded-full bg-white/10 blur-3xl"></div>
<div class="absolute -right-20 -bottom-20 size-96 rounded-full bg-sky-400/20 blur-[100px]"></div>
<div class="relative z-10 mx-auto max-w-3xl">
<h2 class="text-4xl font-bold tracking-tight text-white sm:text-6xl">Ready to secure your space?</h2>
<p class="mt-8 text-lg text-blue-100/80 sm:text-xl">
Join the PhoneCam network today. Set up your first camera in seconds and monitor from anywhere.
</p>
<div class="mt-12 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Button href="/auth/signup" size="lg" class="h-16 rounded-full bg-white px-10 text-lg font-bold text-blue-700 shadow-xl transition-transform hover:scale-105 active:scale-95">
Get started for free
</Button>
<Button href="/auth/login" variant="ghost" class="h-16 rounded-full px-10 text-lg font-semibold text-white hover:bg-white/10">
Operator Login
</Button>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="border-t border-white/5 bg-black/30 px-6 py-20 sm:px-10 lg:px-14">
<div class="mx-auto max-w-7xl">
<div class="grid gap-12 sm:grid-cols-2 lg:grid-cols-4">
<div class="space-y-6">
<div class="flex items-center">
<span class="text-xl font-bold text-white">PhoneCam</span>
</div>
<p class="text-sm leading-relaxed text-slate-500">
Advanced browser-based security intelligence. Built for performance, privacy, and ease of use.
</p>
<div class="flex gap-4">
<a href="https://github.com" class="text-slate-500 transition-colors hover:text-white">
<GitFork class="size-5" />
</a>
<a href="/" class="text-slate-500 transition-colors hover:text-white">
<Globe class="size-5" />
</a>
</div>
</div>
<div>
<h4 class="text-sm font-bold uppercase tracking-widest text-white">Platform</h4>
<ul class="mt-6 space-y-4">
<li><a href="/app" class="text-sm text-slate-500 transition-colors hover:text-white">Live Dashboard</a></li>
<li><a href="/app" class="text-sm text-slate-500 transition-colors hover:text-white">Motion Events</a></li>
<li><a href="/app" class="text-sm text-slate-500 transition-colors hover:text-white">Device History</a></li>
<li><a href="/app" class="text-sm text-slate-500 transition-colors hover:text-white">Security Logs</a></li>
</ul>
</div>
<div>
<h4 class="text-sm font-bold uppercase tracking-widest text-white">Resources</h4>
<ul class="mt-6 space-y-4">
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Documentation</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">API Reference</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Community</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Status</a></li>
</ul>
</div>
<div>
<h4 class="text-sm font-bold uppercase tracking-widest text-white">Company</h4>
<ul class="mt-6 space-y-4">
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">About Us</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Privacy Policy</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Terms of Service</a></li>
<li><a href="/" class="text-sm text-slate-500 transition-colors hover:text-white">Contact</a></li>
</ul>
</div>
</div>
<div class="mt-20 flex flex-col items-center justify-between gap-6 border-t border-white/5 pt-10 sm:flex-row">
<p class="text-xs text-slate-600">
© 2026 PhoneCam Intelligence Systems. All rights reserved.
</p>
<p class="text-xs text-slate-600">
Final Year Project • Developed by Matiss
</p>
</div>
</div>
</footer>
</div>
<style>
:global(:root) {
--landing-display: 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
--landing-sans: 'Inter Variable', 'Inter', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
}
:global(body) {
background-color: #020617;
background-color: #0a0910;
}
</style>