feat: add landing page and dedicated auth screens

This commit is contained in:
2026-04-17 15:50:00 +01:00
parent fac6409ec4
commit e9f4f67eee
11 changed files with 658 additions and 96 deletions

View File

@@ -1 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<title>PhoneCam</title>
<rect width="512" height="512" rx="120" fill="#0A0A0C" />
<rect x="28" y="28" width="456" height="456" rx="96" fill="#111827" stroke="#2563EB" stroke-width="8" />
<text
x="256"
y="230"
fill="#F8FAFC"
font-family="DejaVu Sans, Arial, sans-serif"
font-size="88"
font-weight="700"
text-anchor="middle"
>
Phone
</text>
<text
x="256"
y="330"
fill="#60A5FA"
font-family="DejaVu Sans, Arial, sans-serif"
font-size="88"
font-weight="700"
text-anchor="middle"
>
Cam
</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -0,0 +1,135 @@
<script lang="ts">
// @ts-nocheck
import { appController } from '$lib/app/controller';
import { appState } from '$lib/app/store';
import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import { ArrowRight } from '@lucide/svelte';
let { mode = 'login' } = $props<{ mode?: 'login' | 'signup' }>();
const isSignup = () => mode === 'signup';
const title = () => (isSignup() ? 'Create your PhoneCam account' : 'Sign in to PhoneCam');
const description = () =>
isSignup()
? 'Start setting up browser-based monitoring and client viewing in a few steps.'
: 'Return to your live monitoring workspace and pick up where you left off.';
const primaryLabel = () => (isSignup() ? 'Create account' : 'Sign in');
const secondaryLabel = () => (isSignup() ? 'I already have an account' : 'Create an account');
const secondaryHref = () => (isSignup() ? '/auth/login' : '/auth/signup');
$effect(() => {
appController.setAuthMode(isSignup());
});
</script>
<section class="mx-auto flex min-h-full w-full max-w-6xl items-center px-6 py-8 sm:px-10 lg:px-12">
<div class="grid w-full gap-10 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
<div class="space-y-8">
<div class="inline-flex items-center gap-3 rounded-full border border-white/10 bg-white/5 px-4 py-2 text-[11px] font-medium uppercase tracking-[0.28em] text-sky-100/70">
<span class="h-2 w-2 rounded-full bg-sky-400 shadow-[0_0_12px_rgba(56,189,248,0.8)]"></span>
PhoneCam Browser Console
</div>
<div class="space-y-5">
<h1 class="max-w-xl text-4xl font-semibold tracking-[-0.04em] text-white sm:text-5xl lg:text-6xl">
Turn any browser into a live security surface.
</h1>
<p class="max-w-xl text-base leading-7 text-slate-300 sm:text-lg">
Authenticate once, assign this browser as a camera or client, and manage motion alerts, recordings, and live feeds from a single control plane.
</p>
</div>
<div class="grid gap-4 border-t border-white/10 pt-6 text-sm text-slate-300 sm:grid-cols-3">
<div class="space-y-2">
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500">Assign roles</p>
<p>Register this browser as a camera station or a client viewer without leaving the web app.</p>
</div>
<div class="space-y-2">
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500">Stay live</p>
<p>Watch feeds, monitor connection health, and see motion activity arrive in real time.</p>
</div>
<div class="space-y-2">
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500">Carry context</p>
<p>Your saved device and session restore cleanly so returning to the workspace is fast.</p>
</div>
</div>
</div>
<Card variant="glass" class="overflow-hidden rounded-[2rem] border-white/10 bg-black/45 backdrop-blur-xl">
<div class="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-sky-400/50 to-transparent"></div>
<CardHeader class="gap-5 px-7 pt-7 text-center">
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80">PhoneCam</p>
<div class="space-y-2">
<CardTitle class="text-3xl font-semibold tracking-tight text-white">{title()}</CardTitle>
<CardDescription class="text-sm leading-6 text-slate-400">{description()}</CardDescription>
</div>
</CardHeader>
<CardContent class="space-y-4 px-7">
<div class="space-y-2">
<Label class="sr-only" for="authEmail">Email</Label>
<Input
id="authEmail"
type="email"
placeholder="Email address"
class="h-12 rounded-2xl border-white/10 bg-white/[0.04] text-sm text-white placeholder:text-slate-500"
value={$appState.authForm.email}
oninput={(event) => appController.setAuthField('email', (event.currentTarget as HTMLInputElement).value)}
/>
</div>
<div class="space-y-2">
<Label class="sr-only" for="authPassword">Password</Label>
<Input
id="authPassword"
type="password"
placeholder="Password"
class="h-12 rounded-2xl border-white/10 bg-white/[0.04] text-sm text-white placeholder:text-slate-500"
value={$appState.authForm.password}
oninput={(event) => appController.setAuthField('password', (event.currentTarget as HTMLInputElement).value)}
/>
</div>
{#if isSignup()}
<div id="authNameField" class="space-y-2">
<Label class="sr-only" for="authName">Name</Label>
<Input
id="authName"
type="text"
placeholder="Display name"
class="h-12 rounded-2xl border-white/10 bg-white/[0.04] text-sm text-white placeholder:text-slate-500"
value={$appState.authForm.name}
oninput={(event) => appController.setAuthField('name', (event.currentTarget as HTMLInputElement).value)}
/>
</div>
{/if}
</CardContent>
<CardFooter class="flex flex-col items-stretch gap-4 px-7 pb-7 pt-2">
<Button
id="signInBtn"
variant="premium"
class="h-12 w-full rounded-2xl text-base shadow-[0_24px_60px_rgba(37,99,235,0.26)]"
onclick={() => appController.submitAuth()}
>
{primaryLabel()}
</Button>
<div class="flex items-center justify-between gap-4 rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-400">
<span>{isSignup() ? 'Already provisioned?' : 'Need a new workspace?'}</span>
<Button
href={secondaryHref()}
variant="ghost"
class="h-auto rounded-full px-0 text-sm font-medium text-white hover:bg-transparent hover:text-sky-200"
>
{secondaryLabel()}
<ArrowRight class="ml-2 size-4" />
</Button>
</div>
</CardFooter>
</Card>
</div>
</section>

View File

@@ -0,0 +1,5 @@
import { redirect } from '@sveltejs/kit';
export function load() {
throw redirect(307, '/auth/login');
}

View File

@@ -0,0 +1,9 @@
<script lang="ts">
// @ts-nocheck
import AuthPanel from '$lib/auth/AuthPanel.svelte';
import AppChrome from '$lib/sim/ui/AppChrome.svelte';
</script>
<AppChrome pageKey="auth">
<AuthPanel mode="login" />
</AppChrome>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
// @ts-nocheck
import AuthPanel from '$lib/auth/AuthPanel.svelte';
import AppChrome from '$lib/sim/ui/AppChrome.svelte';
</script>
<AppChrome pageKey="auth">
<AuthPanel mode="signup" />
</AppChrome>

View File

@@ -1,90 +1,457 @@
<script lang="ts"> <script lang="ts">
// @ts-nocheck // @ts-nocheck
import AppChrome from '$lib/sim/ui/AppChrome.svelte';
import { appController } from '$lib/app/controller';
import { appState } from '$lib/app/store';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card/index.js'; import {
import { Input } from '$lib/components/ui/input/index.js'; ArrowRight,
import { Label } from '$lib/components/ui/label/index.js'; BellRing,
import { Separator } from '$lib/components/ui/separator/index.js'; Camera,
import { ShieldCheck } from '@lucide/svelte'; MonitorSmartphone,
Radar,
Shield,
Sparkles,
ChevronRight,
Activity,
Lock,
Zap,
Globe,
GitFork
} from '@lucide/svelte';
import { fade, fly, scale } from 'svelte/transition';
import { onMount } from 'svelte';
let mounted = $state(false);
onMount(() => {
mounted = true;
});
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> </script>
<AppChrome pageKey="auth"> <svelte:head>
<section id="screen-auth" class="flex flex-col items-center justify-center min-h-[70vh] animate-fade-in max-w-sm mx-auto"> <title>PhoneCam | Browser-Based Security Intelligence</title>
<Card variant="glass" class="w-full rounded-3xl"> <meta
<CardHeader class="items-center gap-4 px-6 pt-6 text-center"> name="description"
<div content="PhoneCam turns any browser into a professional security console with live motion detection and WebRTC streaming."
class="flex size-20 items-center justify-center rounded-3xl bg-gradient-to-tr from-blue-600 to-indigo-600 shadow-lg shadow-blue-900/20" />
</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>
<!-- 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">
<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"
> >
<ShieldCheck class="size-10 text-white" /> 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>
</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 class="flex flex-col gap-2">
<CardTitle class="text-3xl font-bold tracking-tight text-white">PhoneCam Web</CardTitle>
<CardDescription class="text-sm text-gray-400">Sign in to manage visual security from your browser.</CardDescription>
</div> </div>
</CardHeader>
<CardContent class="flex flex-col gap-4 px-6">
<div class="flex flex-col gap-2">
<Label class="sr-only" for="authEmail">Email</Label>
<Input
id="authEmail"
type="email"
placeholder="Email address"
class="h-12 rounded-xl border-white/10 bg-black/40 text-sm text-white placeholder:text-gray-500"
value={$appState.authForm.email}
oninput={(event) => appController.setAuthField('email', (event.currentTarget as HTMLInputElement).value)}
/>
</div> </div>
<div class="flex flex-col gap-2">
<Label class="sr-only" for="authPassword">Password</Label>
<Input
id="authPassword"
type="password"
placeholder="Password"
class="h-12 rounded-xl border-white/10 bg-black/40 text-sm text-white placeholder:text-gray-500"
value={$appState.authForm.password}
oninput={(event) => appController.setAuthField('password', (event.currentTarget as HTMLInputElement).value)}
/>
</div> </div>
{#if $appState.isRegistering}
<div id="authNameField" class="flex flex-col gap-2">
<Label class="sr-only" for="authName">Name</Label>
<Input
id="authName"
type="text"
placeholder="Your Name"
class="h-12 rounded-xl border-white/10 bg-black/40 text-sm text-white placeholder:text-gray-500"
value={$appState.authForm.name}
oninput={(event) => appController.setAuthField('name', (event.currentTarget as HTMLInputElement).value)}
/>
</div> </div>
{/if} {/if}
</CardContent>
<CardFooter class="flex flex-col items-stretch gap-4 border-0 bg-transparent px-6 pb-6 pt-2">
<Button
id="signInBtn"
variant="premium"
class="h-12 w-full rounded-xl text-base shadow-lg shadow-blue-900/20"
onclick={() => appController.submitAuth()}
>
{$appState.isRegistering ? 'Create Account' : 'Sign In'}
</Button>
<div class="flex w-full items-center gap-3 text-xs text-gray-600">
<Separator class="flex-1 bg-white/10" />
<span>OR</span>
<Separator class="flex-1 bg-white/10" />
</div> </div>
<Button
id="toggleAuthModeBtn"
variant="ghost"
class="h-12 w-full rounded-xl border border-white/5 text-gray-400 hover:bg-white/5 hover:text-white"
onclick={() => appController.toggleAuthMode()}
>
{$appState.isRegistering ? 'I already have an account' : 'Create an account'}
</Button>
</CardFooter>
</Card>
</section> </section>
</AppChrome>
<!-- 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">
<svelte:component this={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}">
<svelte:component this={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(body) {
background-color: #020617;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,16 +1,27 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>PhoneCam</title>
<rect width="512" height="512" rx="120" fill="#0A0A0C" /> <rect width="512" height="512" rx="120" fill="#0A0A0C" />
<rect x="84" y="84" width="344" height="344" rx="92" fill="url(#bg)"/> <rect x="28" y="28" width="456" height="456" rx="96" fill="#111827" stroke="#2563EB" stroke-width="8" />
<rect x="116" y="132" width="280" height="248" rx="48" fill="#15161D" stroke="rgba(255,255,255,0.08)" stroke-width="8"/> <text
<rect x="146" y="164" width="220" height="160" rx="28" fill="#0E1016"/> x="256"
<circle cx="256" cy="244" r="52" fill="#2563EB"/> y="230"
<circle cx="256" cy="244" r="28" fill="#0A0A0C"/> fill="#F8FAFC"
<path d="M186 122C186 108.745 196.745 98 210 98H302C315.255 98 326 108.745 326 122V144H186V122Z" fill="#2563EB"/> font-family="DejaVu Sans, Arial, sans-serif"
<path d="M220 290L256 326L322 214" stroke="white" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/> font-size="88"
<defs> font-weight="700"
<linearGradient id="bg" x1="84" y1="84" x2="428" y2="428" gradientUnits="userSpaceOnUse"> text-anchor="middle"
<stop stop-color="#1D4ED8"/> >
<stop offset="1" stop-color="#1E40AF"/> Phone
</linearGradient> </text>
</defs> <text
x="256"
y="330"
fill="#60A5FA"
font-family="DejaVu Sans, Arial, sans-serif"
font-size="88"
font-weight="700"
text-anchor="middle"
>
Cam
</text>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 991 B

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB