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); --color-sidebar-ring: var(--sidebar-ring);
/* Custom brand colors and glassmorphism tokens */ /* Custom brand colors and glassmorphism tokens */
--color-premium: #2563eb; --color-premium: #ff7a59;
--color-premium-foreground: #ffffff; --color-premium-foreground: #17110d;
--color-glass-background: rgb(25 25 30 / 60%); --color-glass-background: rgb(25 25 30 / 60%);
--color-glass-border: rgb(255 255 255 / 8%); --color-glass-border: rgb(255 255 255 / 8%);
--color-glass-panel: rgb(15 15 20 / 70%); --color-glass-panel: rgb(15 15 20 / 70%);

View File

@@ -1,457 +1,342 @@
<script lang="ts"> <script lang="ts">
// @ts-nocheck // @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 { 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(() => { onMount(() => {
mounted = true; if (!shaderCanvas) {
return;
}
const gl = shaderCanvas.getContext('webgl', {
alpha: true,
antialias: true,
premultipliedAlpha: false
}); });
const operationalRoles = [ if (!gl) {
{ return;
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'
} }
];
const features = [ const programBundle = createProgram(gl, vertexShaderSource, fragmentShaderSource);
{ if (!programBundle) {
title: 'Zero Latency', return;
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 workflow = [ const { program, vertexShader, fragmentShader } = programBundle;
{ const positionLocation = gl.getAttribLocation(program, 'a_position');
kicker: '01', const timeLocation = gl.getUniformLocation(program, 'u_time');
title: 'Authenticate', const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
copy: 'Create an operator account or sign in to restore your saved browser session.'
}, const positionBuffer = gl.createBuffer();
{ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
kicker: '02', gl.bufferData(
title: 'Assign this browser', gl.ARRAY_BUFFER,
copy: 'Register it as a camera or a client so routing, controls, and dashboards match the role.' new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
}, gl.STATIC_DRAW
{ );
kicker: '03',
title: 'Run the workspace', let frameId = 0;
copy: 'Monitor feeds, motion alerts, activity history, and device health without leaving the app shell.' let lastWidth = 0;
let lastHeight = 0;
const resize = () => {
if (!shaderCanvas) {
return;
} }
];
const evidence = [ const dpr = Math.min(window.devicePixelRatio || 1, 2);
'Live browser camera preview and role-based dashboards', const width = Math.floor(shaderCanvas.clientWidth * dpr);
'Motion event notifications, activity history, and recording playback', const height = Math.floor(shaderCanvas.clientHeight * dpr);
'Persistent device restore so returning to the app is fast'
]; 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> </script>
<svelte:head> <svelte:head>
<title>PhoneCam | Browser-Based Security Intelligence</title> <title>PhoneCam | Browser-Based Security Intelligence</title>
<meta <meta
name="description" 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> </svelte:head>
<div class="relative min-h-screen overflow-x-hidden bg-[#020617] font-sans text-slate-200 selection:bg-sky-500/30"> <section
<!-- Grainy texture overlay --> class="relative min-h-screen overflow-hidden bg-[#0a0910] text-[#f6efe7]"
<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> 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="relative z-10 flex min-h-screen flex-col">
<div class="fixed inset-0 z-0 overflow-hidden pointer-events-none"> <header class="px-6 py-5 sm:px-8 lg:px-10">
<div class="absolute -top-[10%] -left-[10%] h-[40%] w-[40%] rounded-full bg-sky-500/10 blur-[120px]"></div> <div class="mx-auto flex max-w-6xl items-center justify-between">
<div class="absolute top-[20%] -right-[5%] h-[35%] w-[35%] rounded-full bg-indigo-500/10 blur-[120px]"></div> <a
<div class="absolute bottom-[10%] left-[20%] h-[30%] w-[30%] rounded-full bg-blue-600/5 blur-[100px]"></div> href="/"
</div> class="text-xl font-semibold tracking-tight text-[#f6efe7]"
style="font-family: var(--landing-display);"
<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"> PhoneCam
{#if mounted} </a>
<div in:fly={{ y: -20, duration: 800 }} class="hidden sm:block"> <nav class="flex items-center gap-2">
<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">
<Button <Button
href="/auth/login" href="/auth/login"
variant="ghost" 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 Sign in
</Button> </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"> <Button
<span class="relative z-10 flex items-center gap-2 font-semibold"> href="/auth/signup"
variant="premium"
class="rounded-full px-5 text-sm font-semibold"
>
Get started 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> </Button>
</nav> </nav>
{/if}
</div> </div>
</header> </header>
<main class="relative z-10"> <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">
<!-- Hero Section --> <div in:fly={{ y: 28, duration: 500 }} class="max-w-3xl space-y-8">
<section class="px-6 pb-24 pt-12 sm:px-10 lg:px-14"> <p class="text-xs font-semibold uppercase tracking-[0.32em] text-[#ff7a59]">
<div class="mx-auto grid max-w-7xl gap-16 lg:grid-cols-[1fr_1.1fr] lg:items-center"> Live browser monitoring
{#if mounted} </p>
<div in:fly={{ x: -30, duration: 1000, delay: 200 }} class="space-y-10"> <div class="space-y-5">
<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"> <p
<Sparkles class="size-3.5" /> class="text-[clamp(3.2rem,10vw,7rem)] leading-none tracking-[-0.07em] text-[#f6efe7]"
Live browser-based security style="font-family: var(--landing-display);"
</div> >
PhoneCam
<div class="space-y-6"> </p>
<h1 class="text-5xl font-bold leading-[1.1] tracking-tight text-white sm:text-6xl xl:text-7xl"> <h1
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. 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> </h1>
<p class="max-w-xl text-lg leading-relaxed text-slate-400 sm:text-xl"> <p class="max-w-2xl text-base leading-7 text-[#d7c9bd] sm:text-lg">
PhoneCam turns your devices into professional security stations. No apps to install—just pure WebRTC power for live capture, motion tracking, and remote viewing. Use one browser as the camera station, another as the viewer, and keep feeds,
recordings, and activity in one place.
</p> </p>
</div> </div>
<div class="flex flex-col gap-4 sm:flex-row"> <div class="flex flex-col gap-3 sm:flex-row">
<Button href="/auth/signup" variant="premium" class="h-14 rounded-full px-8 text-base font-bold shadow-xl"> <Button href="/auth/signup" variant="premium" class="h-12 rounded-full px-6 text-sm font-semibold">
Start monitoring now Create account
<ArrowRight class="ml-2 size-5" /> <ArrowRight class="ml-2 size-4" />
</Button> </Button>
<Button <Button
href="/app" href="/app"
variant="ghost" 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 Open dashboard
</Button> </Button>
</div> </div>
<div class="flex flex-wrap items-center gap-x-8 gap-y-4 pt-4"> <div class="flex flex-wrap items-center gap-3 pt-2 text-sm text-[#b7a696]">
{#each evidence as item} {#each heroPoints as item, index}
<div class="flex items-center gap-2 text-xs font-medium text-slate-500"> <span>{item}</span>
<div class="size-1.5 rounded-full bg-sky-500 shadow-[0_0_8px_rgba(56,189,248,0.5)]"></div> {#if index < heroPoints.length - 1}
{item} <span class="text-[#ff7a59]"></span>
</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>
{/if} {/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} {/each}
</div> </div>
</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>
</div> </div>
{/each} </section>
</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> <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) { :global(body) {
background-color: #020617; background-color: #0a0910;
} }
</style> </style>