feat: replace landing page with shader hero
This commit is contained in:
@@ -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%);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
if (!gl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const programBundle = createProgram(gl, vertexShaderSource, fragmentShaderSource);
|
||||
if (!programBundle) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 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);
|
||||
};
|
||||
});
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
|
||||
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 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 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'
|
||||
];
|
||||
</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">
|
||||
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
|
||||
href="/auth/signup"
|
||||
variant="premium"
|
||||
class="rounded-full px-5 text-sm font-semibold"
|
||||
>
|
||||
Get started
|
||||
</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.
|
||||
</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>
|
||||
</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" />
|
||||
</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]"
|
||||
>
|
||||
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>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<!-- 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 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-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="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>
|
||||
<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-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-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}
|
||||
{/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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user